【问题标题】:Manually specify the value of a primary key in JPA @GeneratedValue column在 JPA @GeneratedValue 列中手动指定主键的值
【发布时间】:2012-08-13 16:38:54
【问题描述】:

我有一个实体,它有一个主键/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 自动生成)。

有没有办法手动指定具有 @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(因此它将用于在数据库中创建记录)的方法。

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

我想尝试的想法和选择已经不多了。任何? (开始赏金)

【问题讨论】:

  • 您是否有理由不能通过 SQL 从旧 -> 新进行标准导入?一旦你完成了,你只需要调整序列,这样你就不会覆盖这些值。毕竟,它是一个 ORM,它正在读取数据库。
  • 新旧有完全不同的模式,应用程序用于将数据从一个模型转换到另一个模型(例如一个字段用于旧的名字和姓氏,两个字段在新的等等)。当然,转储旧数据库、创建新数据脚本并导入它们是另一种选择;但是,该应用程序几乎已完成。此外,我真的很想知道如何实现这一点。
  • 既然你的模式完全不同,为什么只是做一个“干净”的导入会出现问题。又名:建立新的实体和关系并保持它们?我要引导你的地方是,我认为你在吠叫一棵困难的树。但是,您可以删除序列,手动保留所有内容,然后启动序列。
  • @GeneratedValue(strategy = GenerationType.AUTO) 在我的情况下成功了。先迁移旧数据,保留现有密钥。

标签: java mysql jpa orm eclipselink


【解决方案1】:

这适用于 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 为空,eclipselink 将生成 id。否则将使用现有的 id。

【讨论】:

    【解决方案2】:

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

    对于 IDENTITY,我什至不确定 MySQL 是否允许您提供自己的 ID,您可能需要验证这一点。一般来说,我永远不会推荐使用 IDENTITY,因为它不支持预分配。

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

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

    【讨论】:

      【解决方案3】:

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

      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. 创建一个触发器,如果​​它不为空,则将表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`;
        

      警告

      如果手动指定的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;
      

      【讨论】:

      • 这非常好,很有帮助。我们已经在一个 mySql 数据库上成功地实现了这个解决方案。
      • 很高兴它很有用。我写这个答案已经有一段时间了。通常在 ORM 特定工具(SequenceSessionCustomizer)和数据库黑客之间,第二个往往可以更快地完成任务,并且未知的后果更少
      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        在 PostgreSQL 和 EclipseLink 中使用 GenerationType.SEQUENCE 对我有用。

        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 设置为您的实体。

        希望还不算太晚!

        【讨论】:

          【解决方案6】:

          查找自定义 ID 生成器

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

          也许这会有所帮助。

          【讨论】:

          • 很抱歉,这是 Hibernate 特有的(正在使用来自 hibernate 的特殊注释和类)。我正在使用 EclipseLink(而且我无法摆脱它,因为大部分项目都在使用 eclipselink 特定的注释和类)。
          • 我认为 EclipseLink 也有一个叫做 CustomSequencing 的东西。
          • 感谢正确的链接,但是,自定义排序似乎不起作用。无法想象这是一个如此罕见的用例!
          • @scravy 这应该可以:发布您的代码。没有理由自定义序列不能解决问题。
          猜你喜欢
          • 2018-04-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-12
          • 2014-04-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多