Java-JPA-@Version批注


118

@Version批注在JPA中如何工作?

我找到了各种答案,摘录如下:

JPA使用实体中的版本字段来检测对同一数据存储记录的并发修改。当JPA运行时检测到尝试同时修改同一记录的尝试时,它将向尝试最后提交的事务抛出异常。

但是我仍然不确定它是如何工作的。


同样从以下几行开始:

您应该考虑版本字段是不变的。更改字段值会产生不确定的结果。

这是否意味着我们应该将version字段声明为final


1
它所做的只是在每个更新查询中检查/更新版本UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]。如果有人更新了该记录,old.version它将不再与数据库中的记录匹配,并且where子句将阻止更新发生。“行更新”将为0,JPA可以检测到该行以得出发生并发修改的结论。
Stijn de Witt

Answers:


189

但是我仍然不确定它是如何工作的?

假设一个实体MyEntity具有带注释的version属性:

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

更新时,带有注释的字段@Version将增加并添加到WHERE子句中,如下所示:

UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))

如果该WHERE子句与记录不匹配(因为同一实体已经被另一个线程更新),则持久性提供程序将抛出OptimisticLockException

这是否意味着我们应该将我们的version字段声明为final

不可以,但是您可以考虑使二传手受到保护,因为您不应该称呼它。


5
我在这段代码中遇到了一个空指针异常,因为Long初始化为null,可能应该用0L明确初始化。
Markus 2012年

2
不要依赖于自动拆箱long或对其进行调用longValue()。您需要显式null检查。我自己不会明确初始化它,因为该字段应该由ORM提供程序管理。我认为它是0L在第一次插入数据库时​​设置的,因此如果将其设置null为此,则表明该记录尚未持久。
Stijn de Witt 2015年

这样是否可以防止创建一个实体(尝试创建)超过一次?我的意思是假设JMS主题消息触发了实体的创建,并且有多个应用程序实例侦听该消息。我只是想避免出现唯一的违反约束错误
。.– Manu

抱歉,我意识到这是一个旧线程,但是@Version是否也考虑了集群环境中的脏缓存?
肖恩·伊恩·史密斯

3
如果使用Spring Data JPA,则最好对该字段使用包装器Long而不是基元,因为它可能会将空版本字段视为该实体是新实体的指示符。如果实体是新实体(版本为null指示),Spring将调用。但是,如果version具有值,则它将调用。这就是为什么声明为包装类型的版本字段应保持未初始化状态的原因。long@VersionSimpleJpaRepository.save(entity)em.persist(entity)em.merge(entity)
rdguam

33

尽管@Pascal答案是完全有效的,但根据我的经验,我发现以下代码有助于完成乐观锁定:

@Entity
public class MyEntity implements Serializable {    
    // ...

    @Version
    @Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
    private long version = 0L;

    // ...
}

为什么?因为:

  1. 如果带注释的字段意外设置为,乐观锁定将不起作用@Versionnull
  2. 由于此特殊字段不一定是对象的商业版本,为避免引起误解,我更喜欢将该字段命名为optlock而不是version

第一点不要紧,如果应用程序使用 JPA将数据插入到数据库中,作为JPA的供应商将强制执行0@version在创建时间字段。但几乎总是使用普通的SQL语句(至少在单元和集成测试期间)。


我称它u_lmod(用户最后修改),其中用户可以是人工的也可以是定义的(自动化的)流程/实体/应用程序(因此,另外u_lmod_id进行存储也很有意义)。(我认为这是非常基本的业务元属性)。通常将其值存储为UTC中的DATE(TIME)(表示有关年...毫秒的信息)(如果编辑器的时区“位置”对于存储时区很重要,则使用时区)。另外对于DWH同步方案非常有用。
安德里亚斯·迪特里希

7
我认为应在数据库类型中使用bigint而不是整数来匹配长Java类型。
德米特里

很好,Dmitry,谢谢,尽管当涉及到恕我直言的版本时,这并不重要。
G. Demecki

9

每次在数据库中更新实体时,版本字段都会增加一个。更新数据库中实体的每个操作都将附加WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE到其查询中。

在检查操作的受影响行时,jpa框架可以确保在加载和持久存储实体之间没有并发修改,因为当在加载和持久存储之间版本号增加时,查询不会在数据库中找到您的实体。


不通过JPAUpdateQuery,不是。因此,“每个更新数据库中实体的操作都将在其查询中附加WHERE版本= VERSION_THAT_WAS_LOADED_FROM_DATABASE”。
Pedro Borges

2

用于确保一次仅更新一次的版本。JPA提供程序将检查版本,如果预期版本已经增加,则其他人已经更新了实体,因此将引发异常。

因此,更新实体价值将更安全,更乐观。

如果该值频繁更改,则您可能会考虑不使用版本字段。例如,“具有计数器字段的实体,每次访问网页时都会增加”


0

只需添加更多信息。

JPA可以为您管理版本,但是当您通过来更新记录时,JPA不会这样做JPAUpdateClause,在这种情况下,您需要手动将版本增量添加到查询中。

对于通过JPQL进行更新,也可以说相同,即,不是对实体的简单更改,而是对数据库的更新命令,即使该命令是由休眠完成的也是如此

佩德罗


3
什么JPAUpdateClause
Karl Richter

好的问题,简单的答案是:错误的示例:) JPAUpdateClause是QueryDSL特定的,请考虑使用JPQL运行更新,而不是简单地影响代码中实体的状态。
Pedro Borges
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.