
在并发环境中,为了维护数据的一致性,乐观锁是一种常用的策略。hibernate通过在实体类中引入 @version 注解来实现乐观锁。当一个实体被更新时,其 @version 字段会自动递增。如果在更新操作提交时,发现数据库中的版本号与加载时的版本号不一致,则说明该实体已被其他事务修改,hibernate会抛出 optimisticlockexception,从而避免“脏写”问题。
除了标准的乐观锁行为,Hibernate还提供了更精细的锁模式,例如 LockModeType.OPTIMISTIC_FORCE_INCREMENT。这种锁模式的特殊之处在于,它会强制递增实体的版本号,即使该实体在当前事务中并没有被实际修改。这在某些场景下非常有用,例如当一个父实体虽然自身未被修改,但其子集合发生了变化,需要通过递增父实体的版本号来通知其他并发事务。
当我们在使用 LockModeType.OPTIMISTIC_FORCE_INCREMENT 锁模式时,如果查询中通过 LEFT JOIN FETCH 等方式急加载了关联实体,并且这些关联实体没有定义 @Version 字段,就可能触发 cannot force version increment on non-versioned entity 异常。
让我们结合提供的代码示例来具体分析:
@Repository
interface RootEntityRepository extends JpaRepository<RootEntity, String>{
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) // 强制版本递增锁
@Query("SELECT re FROM RootEntity re LEFT JOIN FETCH re.collects WHERE re.id=:id") // 急加载 collects
Optional<RootEntity> findById(String id);
}
@Entity @Getter @Setter
class RootEntity{
@Id
private String id;
@OneToMany(...)
private Set<Collect> collects = new HashSet<>();
@Version private Long version; // RootEntity 具有版本字段
}
@Entity @Getter @Setter
class Collect{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
private Set<EmbeddedCollect> embeddedCollects = new HashSet<>();
@ManyToOne private RootEntity root;
// Collect 实体没有 @Version 字段
}在这个例子中:
异常原因: 当 findById 方法被调用时,LockModeType.OPTIMISTIC_FORCE_INCREMENT 会指示 Hibernate 强制递增所有被加载并处于持久化上下文中的、且被认为需要进行版本控制的实体。由于 LEFT JOIN FETCH 将 Collect 实体也加载到了持久化上下文中,Hibernate 尝试对 Collect 实体执行版本递增操作。但 Collect 实体没有 @Version 字段,因此Hibernate无法执行此操作,从而抛出 cannot force version increment on non-versioned entity 异常。
针对此问题,主要有两种解决方案,选择哪种取决于您的业务需求:
如果您的业务逻辑中,只有 RootEntity 需要进行版本控制,并且只有在 RootEntity 自身被修改时才需要递增其版本号(或者当其子集合发生变化时,默认的乐观锁机制会自动处理父实体的版本递增),那么 LockModeType.OPTIMISTIC_FORCE_INCREMENT 可能是多余的。
实现方式: 从 RootEntityRepository.findById 方法中移除 @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) 注解。
@Repository
interface RootEntityRepository extends JpaRepository<RootEntity, String>{
// 移除 @Lock 注解
@Query("SELECT re FROM RootEntity re LEFT JOIN FETCH re.collects WHERE re.id=:id")
Optional<RootEntity> findById(String id);
}优点:
注意事项:
如果您的业务需求确实要求 Collect 实体也参与版本控制,并且在某些操作下需要对其进行强制版本递增(尽管这在集合元素中不常见),那么可以为 Collect 实体添加 @Version 字段。
实现方式: 在 Collect 实体类中添加一个 @Version 字段。
@Entity @Getter @Setter
class Collect{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
private Set<EmbeddedCollect> embeddedCollects = new HashSet<>();
@ManyToOne private RootEntity root;
@Version private Long version; // 为 Collect 实体添加版本字段
}优点:
注意事项:
cannot force version increment on non-versioned entity 异常是由于 Hibernate 在尝试对一个没有 @Version 字段的实体强制递增版本号时发生的。解决此问题的关键在于理解 LockModeType.OPTIMISTIC_FORCE_INCREMENT 的作用范围以及 LEFT JOIN FETCH 对持久化上下文的影响。
最佳实践建议:
通过以上分析和解决方案,您应该能够有效解决 cannot force version increment on non-versioned entity 异常,并更好地理解Hibernate的乐观锁机制。
以上就是Hibernate乐观锁强制递增与非版本化实体冲突:成因与解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号