Подтвердить что ты не робот

Вручную укажите значение первичного ключа в столбце JPA @GeneratedValue

У меня есть Entity, у которого есть первичный ключ /id, например:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Это хорошо работает. Я использую EclipseLink для создания DDL-схемы, и столбец правильно создается следующим образом:

`id` bigint(20) NOT NULL AUTO_INCREMENT

Однако у меня есть несколько объектов, для которых я хочу сам указать PK (это небольшое приложение, которое передает данные из старой базы данных в новую, которую мы создаем). Если я укажу ID для POJO (используя setId(Long id)) и сохраняю его, EclipseLink не сохраняет его (т.е. Запись сохраняется, но идентификатор автоматически генерируется eclipseLink).

Можно ли вручную указать значение столбца с именем @GeneratedValue?

Вот некоторые мысли по этому вопросу:

Я попытался обойти проблему, вообще не используя @GeneratedValue, но просто вручную определите столбец AUTO_INCREMENTed. Однако это заставляет меня вручную предоставлять идентификаторы всегда, поскольку EclipseLink проверяет первичный ключ (поэтому он может быть не нулевым, нулевым или отрицательным числом). Сообщение об исключении читает, что я должен указать eclipselink.id_validation, однако это, похоже, не имеет никакого значения (я аннотировал @PrimaryKey(validation = IdValidation.NONE), но все же получил то же сообщение).

Чтобы уточнить: Я использую EclipseLink (2.4.0) как провайдер персистентности, и я не могу отказаться от него (большие части проекта зависят от подсказок конкретных запросов, аннотаций, и классы).


EDIT (В ответ на ответы):

Пользовательское упорядочение: Я попытался реализовать свою собственную последовательность. Я попробовал подклассифицировать DefaultSequence, но EclipseLink скажет мне Internal Exception: org.eclipse.persistence.platform.database.MySQLPlatform could not be found. Но я проверил: класс находится в пути к классам.

Итак, я подклассифицировал другой класс, NativeSequence:

public class MyNativeSequence extends NativeSequence {

    public MyNativeSequence() {
        super();
    }

    public MyNativeSequence(final String name) {
        super(name);
    }


    @Override
    public boolean shouldAlwaysOverrideExistingValue() {
        return false;
    }

    @Override
    public boolean shouldAlwaysOverrideExistingValue(final String seqName) {
        return false;
    }

}

Однако я получаю следующее:

javax.persistence.RollbackException: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.java:102)
    ...
Caused by: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
    at org.eclipse.persistence.exceptions.ValidationException.nullPrimaryKeyInUnitOfWorkClone(ValidationException.java:1451)
    ...

(трассировка стека сокращена для ясности). Это то же самое сообщение, которое я получил раньше. Должен ли я подкласс NativeSequence? Если это так, я не знаю, что реализовать для абстрактных методов в Sequence или StandardSequence.

Также стоит отметить, что просто подклассификация (без переопределения каких-либо методов) класс работает так, как ожидалось. Тем не менее, returing false в shouldAlwaysOverrideExistingValue(...) не генерирует ни одного значения вообще (я прошел через программу и getGeneratedValue() не вызывается один раз).

Кроме того, когда я вставляю как 8 объектов определенного типа в транзакцию, он привел к 11 записям в базе данных (какого черта?!).

EDIT (2012-09-01): У меня все еще нет решения проблемы. Реализация моей собственной последовательности не помогла. Мне нужно, чтобы я мог явно не указывать идентификатор (поэтому он будет автоматически сгенерирован) и иметь возможность явно указывать идентификатор (поэтому он будет использоваться для создания записи в базе данных).

Я попытался определить столбец как auto_increment самостоятельно и ommit @GeneratedValue, однако проверка будет удалена и не позволит мне сохранить такую ​​сущность. Если я укажу значение!= 0 и!= Ноль, mysql будет жаловаться на дублирующий первичный ключ.

У меня заканчиваются идеи и варианты, чтобы попробовать. Любые? (начиная щедрость)

4b9b3361

Ответ 1

Если вы используете последовательность TABLE, то EclipseLink позволит вам переопределить значение (или SEQUENCE, если ваша база данных поддерживает это, MySQL не работает).

Для IDENTITY я даже не уверен, что MySQL предоставит вам свой собственный идентификатор, вы можете проверить это. В общем, я бы никогда не рекомендовал использовать IDENTITY, поскольку он не поддерживает preallocation.

Есть несколько проблем, позволяющих IDENTITY предоставить идентификатор или нет. Один из них заключается в том, что в зависимости от значения идентификатора необходимо создать два разных SQL файла вставки, так как для IDENTITY идентификатор не может быть во вставке вообще. Возможно, вы захотите зарегистрировать ошибку, чтобы обеспечить поддержку идентификатора IDENTITY.

Вы все равно можете заставить его работать с вашим собственным подклассом Sequence или, возможно, с подклассом MySQLPlatform. Вы должны установить свой подклассу MySQLPlatform, используя свойство единицы сохранения "eclipselink.target-database".

Ответ 2

Базовое решение вашей проблемы:

  • Создайте дополнительный столбец с нулевым значением в таблице. Он будет содержать назначенные вручную идентификаторы:

    CREATE TABLE `test_table`
    (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `manual_id` bigint(20) NULL,
      `some_other_field` varchar(200) NOT NULL,
      PRIMARY KEY(id)
    );
    
  • Сопоставьте этот столбец с обычным полем вашего объекта:

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="manual_id")
    private Integer manualId;
    
  • Создайте триггер, который устанавливает идентификатор таблицы в назначенный вручную идентификатор, если он не равен null:

    DELIMITER //
    CREATE TRIGGER `test_table_bi` BEFORE INSERT ON `test_table`
      FOR EACH ROW 
      BEGIN
        IF NEW.`manual_id` IS NOT NULL THEN 
          SET NEW.`id` = NEW.`manual_id`;
        END IF;
    END;//
    DELIMITER;    
    
  • Всегда используйте manualId, когда вам нужно назначить пользовательский идентификатор. Триггер сделает для вас магию:

    testEntiy.setManualId(300);
    entityManager.persist(testEntity);
    
  • После фазы импорта базы данных просто удалите триггер, вспомогательный столбец и его отображение.

    DROP TRIGGER `test_table_bi`;
    ALTER TABLE `test_table` DROP COLUMN `manual_id`;
    

Внимание

Если вы вручную укажете id больше текущего значения AUTO_INCREMENT, следующий сгенерированный id перейдет к значению назначенного вручную id плюс 1, например:

INSERT INTO `test_table` (manual_id, some_other_field) VALUES (50, 'Something');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (90, 'Something 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (40, 'Something 3');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 3');

Получит результаты:

+----+-----------+------------------+
| id | manual_id | some_other_field |
+----+-----------+------------------+
| 50 |        50 | Something        |
| 51 |      NULL | Something else   |
| 90 |        90 | Something 2      |
| 91 |      NULL | Something else 2 |
| 40 |        40 | Something 3      |
| 92 |      NULL | Something else 3 |
+----+-----------+------------------+

Чтобы избежать проблем, настоятельно рекомендуется установить столбец AUTO_INCREMENT, чтобы начать с числа, большего, чем все существующие идентификаторы в вашей предыдущей базе данных, например:

ALTER TABLE `test_table` AUTO_INCREMENT = 100000;

Ответ 3

Это работает с eclipselink. Он создаст отдельную таблицу для последовательности, но это не должно представлять проблемы.

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="id", insertable=true, updatable=true, unique=true, nullable=false)
private Long id;

GenerationType.AUTO выберет стратегию идеальной генерации. Поскольку поле указано как вставляемое и обновляемое, будет использоваться стратегия генерации ТАБЛИЦЫ. Это означает, что eclipselink будет генерировать другую таблицу, содержащую текущее значение последовательности, и генерировать сама последовательность, а не делегировать ее в базу данных. Поскольку столбец объявлен вставляемым, если id является нулевым при сохранении, eclipselink будет генерировать идентификатор. В противном случае будет использоваться существующий идентификатор.

Ответ 4

Мне может не хватать что-то очевидное, но почему бы просто не определить другое Entity с тем же аннотацией @Table(name="....."), с теми же полями, но сделать id не сгенерированным? Затем вы можете использовать этот Entity для кода, который копирует данные из старого DB в новый, и тот, у кого есть сгенерированный идентификатор, может использоваться для нормальных созданий, требующих генерации идентификаторов.

Я не могу сказать, работает ли он с EclipseLink, но мы используем Hibernate здесь, и это, похоже, не против.

Ответ 5

Использование GenerationType.SEQUENCE с PostgreSQL и EclipseLink работало для меня.

1) Измените

@GeneratedValue(strategy = GenerationType.IDENTITY) 

@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="step_id_seq")
@SequenceGenerator(name="step_id_seq", sequenceName="step_id_seq")

Теперь вы можете вызвать последовательность, используя NativeQuery:

return ((Vector<Integer>) em.createNativeQuery("select nextval('step_id_seq')::int").getSingleResult()).get(0);

и установите возвращаемый идентификатор в свой объект до вызова метода EntityManager.persist().

Надеюсь, что это не слишком поздно!