【问题标题】:Java - JPA - @Version annotationJava - JPA - @Version 注释
【发布时间】:2011-02-04 01:54:54
【问题描述】:

@Version 注解在 JPA 中是如何工作的?

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

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

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


还有以下几行:

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

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

【问题讨论】:

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

标签: java jpa jpa-annotations


【解决方案1】:

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

假设一个实体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

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

不,但您可以考虑让 setter 受保护,因为您不应该调用它。

【讨论】:

  • 这段代码出现空指针异常,因为 Long 初始化为空,应该用 0L 显式初始化。
  • 不要依赖自动拆箱到long 或只是调用longValue() 就可以了。您需要明确的 null 检查。我不会自己显式初始化它,因为该字段应该由 ORM 提供程序管理。我认为它在第一次插入数据库时​​设置为0L,所以如果它设置为null,这告诉你这条记录还没有被持久化。
  • 这是否会阻止创建一个被(试图)多次创建的实体?我的意思是假设 JMS 主题消息触发了实体的创建,并且有多个应用程序实例侦听该消息。我只是想避免唯一约束违规错误..
  • 对不起,我知道这是一个旧线程,但@Version 是否也考虑到集群环境中的脏缓存?
  • 如果使用 Spring Data JPA,最好为 @Version 字段使用包装器 Long 而不是原始 long,因为 SimpleJpaRepository.save(entity) 可能会处理空版本字段作为该实体是新的指标。如果实体是新的(如版本为空所示),Spring 将调用em.persist(entity)。但是如果版本有一个值,那么它将调用em.merge(entity)。这也是为什么声明为包装类型的版本字段应该保持未初始化的原因。
【解决方案2】:

虽然@Pascal 的回答完全有效,但根据我的经验,我发现下面的代码有助于完成乐观锁定:

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

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

    // ...
}

为什么?因为:

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

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

【讨论】:

  • 我称它为 u_lmod(用户上次修改),其中 用户可以是人或已定义(自动化)的流程/实体/应用程序(所以另外存储u_lmod_id 也很有意义)。 (在我看来,这是一个非常基本的业务元属性)。它通常将其值存储为 UTC 中的 DATE(TIME) (意思是关于年份...毫秒的信息)(如果编辑器的时区“位置”对于存储时区很重要)。对于 DWH 同步场景也非常有用。
  • 我认为在 db 类型中应该使用 bigint 而不是整数来匹配 long java 类型。
  • 好点德米特里,谢谢,虽然当谈到版本控制恕我直言,这并不重要。
  • 嗨,我有一种情况,我在现有字段上引入了@Version,并且该字段中有空值(旧记录)。因此,如果修改了旧记录并且注释试图增加。由于我的列类型是整数,它试图获取 .intValue() 并获取空指针。请告知,TIA。
  • @PrakashPalnati 只需更新您的数据库以设置版本 = 0 其中版本为空?
【解决方案3】:

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

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

【讨论】:

  • 不是通过 JPAUpdateQuery 它没有。所以“更新数据库中实体的每个操作都会在其查询中附加 WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE”是不正确的。
【解决方案4】:

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

因此更新实体值会更安全、更乐观。

如果值变化频繁,那么你可以考虑不使用版本字段。例如“具有计数器字段的实体,每次访问网页时都会增加”

【讨论】:

    【解决方案5】:

    只是添加更多信息。

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

    关于通过 JPQL 更新也可以这样说,即不是对实体的简单更改,而是对数据库的更新命令,即使是由休眠完成的

    佩德罗

    【讨论】:

    • 什么是JPAUpdateClause
    • 好问题,简单答案是:不好的例子 :) JPAUpdateClause 是特定于 QueryDSL 的,请考虑使用 JPQL 运行更新,而不是简单地影响代码中的实体状态。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    • 2017-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多