在JPA @GeneratedValue列中手动指定主键的值

scr*_*avy 30 java mysql orm jpa eclipselink

我有一个具有主键/ id字段的实体,如下所示:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Run Code Online (Sandbox Code Playgroud)

这很好用.我正在使用EclipseLink创建DDL-Schema,并且正确创建了这样的列:

`id` bigint(20) NOT NULL AUTO_INCREMENT
Run Code Online (Sandbox Code Playgroud)

但是,我有几个实体我想要自己指定PK(这是​​一个将数据从旧数据库传输到我们正在构建的新数据库的小应用程序).如果我指定POJO的ID(使用setId(Long id))并保留它,则EclipseLink 不保存它(即保存记录,但id由eclipseLink自动生成).

有没有办法手动指定具有@GeneratedValue的列的值?

这里有一些关于这个问题的想法

我试图通过不使用@GeneratedValue解决问题,而只是手动将列定义为AUTO_INCREMENTed.然而,这迫使我总是手动提供ID ,因为EclipseLink验证主键(因此它可能不是null,零或负数).异常消息显示我应该指定eclipselink.id_validation,但这似乎没有任何区别(我注释@PrimaryKey(validation = IdValidation.NONE)但仍然得到相同的消息).

澄清一下:我正在使用EclipseLink(2.4.0)作为持久性提供程序,我无法切换它(项目的大部分依赖于eclipselink特定的查询提示,注释和类).


编辑(回答答案):

自定义排序:我尝试实现自己的排序.我尝试了子类化DefaultSequence,但EclipseLink会告诉我Internal Exception: org.eclipse.persistence.platform.database.MySQLPlatform could not be found.但我已经检查过:这个类在classpath上.

所以我继承了另一个类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;
    }

}
Run Code Online (Sandbox Code Playgroud)

但是,我得到的是以下内容:

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)
    ...
Run Code Online (Sandbox Code Playgroud)

(为清晰起见缩短了堆栈跟踪).这是我之前收到的相同信息.我不应该继承NativeSequence的子类吗?如果是这样,我不知道在Sequence或StandardSequence中为抽象方法实现什么.

值得注意的是,简单地子类化(不覆盖任何方法)该类按预期工作.然而,ret in false in shouldAlwaysOverrideExistingValue(...)根本不会产生单个值(我逐步执行程序并且getGeneratedValue()不会调用一次).

此外,当我在一个事务中插入8个特定类型的实体时,它在数据库中产生了11条记录(到底是什么?!).

编辑(2012-09-01):我仍然没有问题的解决方案,实现我自己的序列没有解决它.我需要的是一种方法,能够不显式设置Id(因此它将自动生成)并能够显式设置Id(因此它将用于在数据库中创建记录).

我试图将列定义为auto_increment,并省略@GeneratedValue,但是验证将启动并且不允许我保存这样的实体.如果我指定一个值!= 0和!=零,mysql会抱怨重复的主键.

我已经没有想法和选择了.任何?(开始赏金)

poi*_*son 9

这适用于eclipselink.它将为序列创建一个单独的表,但这不应该成为问题.

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

GenerationType.AUTO将选择理想的生成策略.由于该字段被指定为可插入和可更新,因此将使用TABLE生成策略.这意味着eclipselink将生成另一个表,其中包含当前序列值并生成序列本身,而不是将其委托给数据库.由于列是声明可插入的,如果持久化时id为null,则eclipselink将生成id.否则将使用现有的id.


Jam*_*mes 7

如果使用TABLE排序,那么EclipseLink将允许您覆盖该值(如果您的数据库支持,则为SEQUENCE,而MySQL则不支持).

对于IDENTITY,我甚至不确定MySQL是否允许您提供自己的ID,您可能需要验证这一点.一般情况下,我绝不会建议使用IDENTITY,因为它不支持预分配.

允许IDENTITY提供id的问题有几个.一个是需要根据id值生成两个不同的插入SQL,对于IDENTITY,id根本不能在插入中生成.您可能希望记录一个错误,让IDENTITY支持用户提供的ID.

您仍然可以使用自己的Sequence子类或MySQLPlatform子类.您可以使用"eclipselink.target-database"持久性单元属性设置MySQLPlatform子类.


Ant*_*oly 6

以数据库为中心的问题解决方案:

  1. 在表中创建一个辅助的可为空的列.它将保存您手动分配的ID:

    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)
    );
    
    Run Code Online (Sandbox Code Playgroud)
  2. 将此列映射到实体中的普通字段:

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="manual_id")
    private Integer manualId;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 创建一个触发器,如果​​它不为null,则将表id设置为手动分配的id:

    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;    
    
    Run Code Online (Sandbox Code Playgroud)
  4. 始终使用manualId何时需要分配自定义ID.触发器将为您带来魔力:

    testEntiy.setManualId(300);
    entityManager.persist(testEntity);
    
    Run Code Online (Sandbox Code Playgroud)
  5. 在数据库导入阶段之后,简单地删除触发器,辅助列和它的映射.

    DROP TRIGGER `test_table_bi`;
    ALTER TABLE `test_table` DROP COLUMN `manual_id`;
    
    Run Code Online (Sandbox Code Playgroud)

警告

如果手动指定大于当前AUTO_INCREMENT值的id ,则下一个生成的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');
Run Code Online (Sandbox Code Playgroud)

将使用结果:

+----+-----------+------------------+
| 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 |
+----+-----------+------------------+
Run Code Online (Sandbox Code Playgroud)

为避免出现问题,强烈建议将AUTO_INCREMENT列设置为以大于先前数据库中所有现有ID的数字开头,例如:

ALTER TABLE `test_table` AUTO_INCREMENT = 100000;
Run Code Online (Sandbox Code Playgroud)