在并发环境中,为了维护数据的一致性,乐观锁是一种常用的策略。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{ @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) // 强制版本递增锁 @Query("SELECT re FROM RootEntity re LEFT JOIN FETCH re.collects WHERE re.id=:id") // 急加载 collects Optional findById(String id); } @Entity @Getter @Setter class RootEntity{ @Id private String id; @OneToMany(...) private Set
collects = new HashSet<>(); @Version private Long version; // RootEntity 具有版本字段 } @Entity @Getter @Setter class Collect{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ElementCollection private Set@@@###@@@ 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{ // 移除 @Lock 注解 @Query("SELECT re FROM RootEntity re LEFT JOIN FETCH re.collects WHERE re.id=:id") Optional findById(String id); }
优点:
注意事项:
如果您的业务需求确实要求 Collect 实体也参与版本控制,并且在某些操作下需要对其进行强制版本递增(尽管这在集合元素中不常见),那么可以为 Collect 实体添加 @Version 字段。
实现方式: 在 Collect 实体类中添加一个 @Version 字段。
@Entity @Getter @Setter
class Collect{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
private Set@@@###@@@ 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的乐观锁机制。