JPA 何时设置 @GeneratedValue @Id

Posted

技术标签:

【中文标题】JPA 何时设置 @GeneratedValue @Id【英文标题】:When does the JPA set a @GeneratedValue @Id 【发布时间】:2012-02-23 15:53:53 【问题描述】:

我有一个简单的 JPA 实体,它使用生成的 long“ID”作为其主键:

@Entity
public class Player 
   private long id;

   protected Player() 
     // Do nothing; id defaults to 0L
   


   @GeneratedValue
   @Id
   public long getId() 
      return id;
   

   protected void setId(final long id) 
      this.id = id;
   
   // Other code

在这种类型的对象生命周期的某个时刻,JPA 必须调用setId() 来记录生成的 ID 值。我的问题是,什么时候会发生这种情况,说明这一点的文档在哪里。我查看了 JPA 规范,但找不到明确的声明。

JPA 规范说(强调):

托管实体实例是具有持久性身份的实例,当前与持久性上下文相关联。

这是否是说对象必须管理才能使其@Id 显着? EntityManager.persist() 的文档说(强调)它使“实例托管和持久化”,这是否意味着@Id 是由该方法设置的?或者直到你打电话给EntityTransaction.commit()

@Id 的设置可能因不同的 JPA 提供程序而异,也可能因生成策略不同而异。但是,对于已设置的生命周期中的最早点,您可以做出的最安全(便携、符合规范)的假设是什么?

【问题讨论】:

听起来你可以通过调试轻松建立。 我敢打赌,如果规范没有明确说明 何时,则应该生成 @Id,而不是由供应商决定。 @Raedwald:调试会告诉你 JPA 在内部是如何工作的,并告诉你哪些位是特定于方言的。 相关但不重复的问题:***.com/questions/8169640/… 【参考方案1】:

根据JSR 338: JavaTM Persistence 2.1/3.5.3 实体生命周期回调方法的语义

在实体被持久化或删除后,将为实体调用PostPersistPostRemove 回调方法。这些回调也将在这些操作级联到的所有实体上调用。 PostPersistPostRemove 方法将分别在数据库插入和删除操作之后被调用。这些数据库操作可能在调用持久化、合并或删除操作后直接发生,也可能在刷新操作发生后直接发生(可能在事务结束时)。 PostPersist 方法中提供了生成的主键值

一个可能的(个人推测的)例外是GeneratorType.TABLE,容器(可能)获取要使用的值并(可能)在PrePersist之前设置它。我总是在PrePersist 中使用我的id。我不确定此行为是否已指定或可能不适用于任何其他供应商。

重要修改

并非所有应用程序服务器都在PrePersist 之前设置id。你可以追踪JPA_SPEC。

【讨论】:

【参考方案2】:

Enterprise JavaBeans 3.1 by Rubinger and Burke 这本书在第 143 页(添加了重点)说如下:

Java Persistence 还可以配置为在调用persist() 方法时自动生成主键,方法是使用主键字段或设置器顶部的@GeneratedValue 注释。因此,在前面的示例中,如果我们启用了自动密钥生成,我们可以在 persist() 方法完成后查看生成的密钥。

JPA 规范说(强调):

托管实体实例是具有持久性身份的实例,当前与持久性上下文相关联。

还有EntityManager.persist() 制作

托管和持久化的实例

由于@Id 对实体的身份 至关重要,EntityManager.persist() 管理对象的唯一方法是通过生成@Id 来建立其身份。 p>


但是

Rubinger 和 Buke 的明确声明与 Hibernate 的行为不一致。因此,知识渊博的人似乎不同意 JPA 规范的意图。

【讨论】:

【参考方案3】:

调用 .persist() 不会自动设置 id 值。您的 JPA 提供程序将确保在实体最终写入 db 之前设置它。所以你假设在事务提交时将分配 id 是正确的。但这不是唯一可能的情况。当你调用 .flush() 时,同样会发生。

托马斯

更新:请注意 Geek 的评论。 -> 如果使用GenerationType.Identity,在实体写入db之前提供者不会设置id。在这种情况下,id 生成发生在 db 级别的插入过程中。无论如何,JPA 提供者将确保实体在之后更新,并且生成的 id 将在 @Id 注释属性中可用。

【讨论】:

听起来很合理。所以,如果你打电话给EntityManager.flush(),你能相信@Generated@Id已经设置了吗?我在文档中找不到任何线索。 你也可以在hibernate论坛看看这个帖子:forum.hibernate.org/viewtopic.php?p=2384011#p2384011这似乎取决于选择的生成器策略 根据 EntityManager 文档docs.oracle.com/javaee/6/api/javax/persistence/…flush 将“将持久性上下文同步到底层数据库”。这意味着所有池化的插入语句都将写入 db 以同步状态。为此,您的 JPA 提供者需要这些 id 值。所以他们应该在调用flush后可用。不过,有些策略可能会更早设置它们。 我想你已经找到了,但我希望文档更清晰。规范的相应部分是“3.2.4 同步到数据库:持久实体的状态在事务提交时同步到数据库。这种同步涉及将持久实体的任何更新写入数据库...... [包括]分配持久属性或字段的新值...应用程序可以使用flush 方法来强制同步。" @Thomas 您写道“您的 JPA 提供程序将确保在实体最终写入 db 之前设置它”。这并不完全正确,因为如果您使用的是strategy=GenerationType.IDENTITY,那么它实际上只有在实体写入数据库之后才可用,而不是在它写入数据库之前。【参考方案4】:

AFAIK,只有在刷新持久性上下文时才能保证分配 ID。它可能会更早分配,但这取决于生成策略。

【讨论】:

以上是关于JPA 何时设置 @GeneratedValue @Id的主要内容,如果未能解决你的问题,请参考以下文章

JAVA中JPA的主键自增长注解设置

JPA之@GeneratedValue注解(转)

理解JPA注解@GeneratedValue

JPA注解“@GeneratedValue”详解

理解JPA注解@GeneratedValue的使用方法

JPA / Hibernate中@GeneratedValue中的字段生成器有啥用?