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

时间:2012-08-17 08:21:47

标签: java mysql jpa orm eclipselink

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

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

这很有效。我使用EclipseLink创建了DDL-Schema,并且正确地创建了这样的列:

`id` bigint(20) NOT NULL AUTO_INCREMENT

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

有没有办法手动指定具有@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。但是我已经检查过了:班级在课堂上。

所以我继承了另一个类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中为抽象方法实现什么。

值得注意的是,简单地子类化(不覆盖任何方法)该类按预期工作。但是,在shouldAlwaysOverrideExistingValue(...)中撤消false将不会生成单个值(我逐步执行该程序并且getGeneratedValue()未调用一次)。

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

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

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

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

6 个答案:

答案 0 :(得分:8)

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

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

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

答案 1 :(得分:7)

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

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

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

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

答案 2 :(得分: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)
    );
    
  2. 将此列映射到您的实体中的普通字段:

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="manual_id")
    private Integer manualId;
    
  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;    
    
  4. 当您需要分配自定义ID时,请始终使用manualId。触发器将为您带来魔力:

    testEntiy.setManualId(300);
    entityManager.persist(testEntity);
    
  5. 在数据库导入阶段之后,简单地删除触发器,辅助列及其映射。

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

  6. 警告

    如果您手动指定的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列设置为以大于先前数据库中所有现有ID的数字开头,例如:

    ALTER TABLE `test_table` AUTO_INCREMENT = 100000;
    

答案 3 :(得分:3)

我可能会遗漏一些明显的东西,但为什么不只是定义具有相同@Table(name=".....")注释的另一个实体,具有相同的字段,但是不生成id?然后,您可以将该实体用于将数据从旧数据库复制到新数据库的代码,具有生成的ID的实体可用于需要生成ID的正常创建。

我不能告诉你它是否适用于EclipseLink,但是我们在这里使用Hibernate,它似乎并不介意。

答案 4 :(得分:1)

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()方法之前将返回的Id设置为您的实体。

希望现在还不晚!

答案 5 :(得分:0)

查找自定义ID生成器

http://blog.anorakgirl.co.uk/2009/01/custom-hibernate-sequence-generator-for-id-field/

也许这可能会有所帮助。