33

我有一个实体,它有一个主键/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 中的抽象方法实现什么。

还可能值得注意的是,简单地子类化(不覆盖任何方法)该类按预期工作。但是,返回 falseshouldAlwaysOverrideExistingValue(...)根本不会生成单个值(我单步执行了程序并且getGeneratedValue()没有被调用一次)。

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

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

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

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

4

7 回答 7

11

这适用于 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。

于 2012-09-07T13:25:27.303 回答
7

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

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

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

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

于 2012-09-04T14:04:13.250 回答
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. 如果表 ID 不为空,则创建一个触发器,将表 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. manualId当您需要分配自定义 ID 时,请始终使用。触发器将为您施展魔法:

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

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

警告

如果手动指定一个大于当前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');

将使用结果:

+----+-----------+------------------+
| 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;
于 2012-09-06T20:01:25.190 回答
4

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

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

于 2012-09-06T03:05:45.720 回答
2

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

希望还不算太晚!

于 2013-06-13T15:06:40.920 回答
0

寻找自定义 ID 生成器

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

也许这会有所帮助。

于 2012-08-17T08:36:49.190 回答
0

我的方式(MySql)是停用 GeneratedValue:

@Id
//@GeneratedValue
@Column(unique = true, nullable = false, columnDefinition ="BINARY(16)")
private UUID id;

并添加实体:

@PrePersist
protected void onCreation() {
    if (id == null) setId(UUID.randomUUID());
}

现在在我的代码中我可以做(例如在服务上):

String clientID = env.getParam("id");
Entity entity = entityRepository.findFirstById(UUID.fromString(clientID));
//is new?
if (entity==null){
  entity = new Entity();
  entity.setId(UUID.fromString(clientID));//set cumstom ID here
}

Entity entityNew = entityRepository.save(entity); //insert
if (entityNew.getId().equals(entity.getId()) ){
  Log.i("OK!")
}

于 2022-02-24T10:21:48.293 回答