我已经搜索了stackoverflow中的帖子,我希望这不是重复。
我第一次尝试使用乐观锁定,我能够使用Spring管理的LockModeType,但无法自己定义LockMode
以下是代码示例:
我正在使用:
注入持久化上下文@PersistenceContext
private EntityManager entityManager;
第一种方法:使用注释交易
@Transactional
public void updateUserProfile(UserProfile userProfile) {
entityManager.lock(userProfile, LockModeType.OPTIMISTIC); // 1*
entityManager.merge(userProfile);
}
1的异常:java.lang.IllegalArgumentException: entity not in the persistence context
第二种方法:管理交易
public void updateUserProfile(UserProfile userProfile) {
entityManager.getTransaction().begin(); // 2*
entityManager.lock(userProfile, LockModeType.OPTIMISTIC);
entityManager.merge(userProfile);
entityManager.getTransaction().commit();
}
2的异常:Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
第3种方法:由于我在共享entityManager中遇到异常,我也尝试从entityManagerFactory创建EntityManager。
@Transactional
public void updateUserProfile(UserProfile userProfile) {
EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();
em.getTransaction().begin();
em.lock(userProfile, LockModeType.OPTIMISTIC); // 3*
em.merge(userProfile);
em.getTransaction().commit();
}
3点的例外:entity not in the persistence context
在我的应用程序上下文中,我使用org.springframework.orm.jpa.JpaTransactionManager
来定义transactionManager
和org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
来定义entityManagerFactory
提前致谢!
答案 0 :(得分:6)
锁定JPA实体的条件:
您似乎违反了(2) - 尝试锁定已分离的实体。
你可以早点合并()。
重点:
如果修改实体并运行事务,并且实体中有版本属性,标有@Version,则会以实体的粒度自动执行乐观锁定。版本属性已更新,如果数据库中尚未更改,则写入成功。这是常见的简单锁定情况,其中对单个实体的所有写入都被序列化以避免损坏。如果可以独立处理每个实体/记录,则无需设置任何LockMode,因为这是默认行为。仔细看看这个选项 - 我怀疑这符合您的简单要求。
如果您有更复杂的处理并且需要在多个逻辑相关的实体实例上执行读取或写入,作为一致的连贯原子操作,并且在此期间阻止/隔离所有其他写入 - 那么您需要设置自己的锁定模式,因为自动化的单个实体锁定不起作用。您需要仔细设计以连贯方式读取或写入的实体集,并手动设计和实现您自己的手动锁定解决方案 - 可能选择关系层次结构中的最顶层实体来记录所有的“全局”锁定相关的'子'实体(利用其@Version属性)。
任何手动锁定解决方案都需要所有DB写入逻辑来兑现锁定。这意味着在同一实体上运行的其他事务必须了解您的锁定设计,事实上,必须在写入之前尝试取出锁定。锁定不会导致阻塞和序列化的各种写入实际上根本没有锁定。
对于乐观锁定,取出锁定的确切时间是灵活的。在提交&提交之前,不检查锁并将其写入DB。发生冲洗操作。所以你可以在从tx开始到提交的任何时候取出锁。对于悲观锁定,情况恰恰相反 - 您必须保护代码的关键区域,就像您的生活依赖于它一样。通常悲观的lockMode应该被设置为启动事务的em.find()或em.query的一部分 - 或者如果因为托管对象已经在内存中而不可能这样做,那么你应该做一个em.flush()和em.refresh(PESSIMISTIC_WRITE)
= - ):