Java 实体类和记录锁定

Java 实体类和记录锁定,java,locking,entity,entitymanager,Java,Locking,Entity,Entitymanager,我正在研究EntityManager API,并试图了解执行记录锁定的顺序。基本上,当用户决定编辑记录时,我的代码是: entityManager.getTransaction().begin(); r = entityManager.find(Route.class, r.getPrimaryKey()); r.setRoute(txtRoute.getText()); entityManager.persist(r); entityManager.getTransaction().commi

我正在研究EntityManager API,并试图了解执行记录锁定的顺序。基本上,当用户决定编辑记录时,我的代码是:

entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getPrimaryKey());
r.setRoute(txtRoute.getText());
entityManager.persist(r);
entityManager.getTransaction().commit();
根据我的尝试和错误,似乎我需要设置
wEntityManager.entityManager.lock(r,LockModeType.悲观_-READ)
.begin()
之后

我很自然地假设我会使用
wEntityManager.entityManager.lock(r,LockModeType.NONE)在提交之后,但它给了我以下信息:

Exception Description: No transaction is currently active
我还没有尝试将其放在提交之前,但这是否会挫败锁定记录的目的,因为我的目标是避免在50个用户试图同时提交更改时碰撞记录

非常感谢任何关于如何在编辑期间锁定记录的帮助


谢谢大家!

在事务内部执行锁定非常有意义。锁在事务结束时自动释放(提交/回滚)。在事务外部锁定(在JPA的上下文中)是没有意义的,因为释放锁是和事务的结束绑定在一起的。另外,在执行更改和提交事务之后锁定也没有太大意义

这可能是因为您使用悲观锁定的目的不同于它们真正的目的。如果我的假设是错误的,那么你可以忽略答案的结尾。当您的事务对实体(行)持有悲观读锁时,可以保证:

  • 无脏读:其他事务无法看到对锁定行执行的操作的结果
  • 可重复读取:没有来自其他事务的修改
  • 若事务修改了锁定的实体,则悲观读将升级为悲观写,若无法升级锁,则事务失败
下面粗略描述了在事务开始时获得锁定的场景:

entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getPrimaryKey(), 
      LockModeType.PESSIMISTIC_READ);
//from this moment on we can safely read r again expect no changes
r.setRoute(txtRoute.getText());
entityManager.persist(r);
//When changes are flushed to database, provider must convert lock to 
//PESSIMISTIC_WRITE, which can fail if concurrent update
entityManager.getTransaction().commit();
通常,数据库不单独支持悲观读取,所以您实际上持有自悲观读取以来的行锁。另外,只有在锁定的行不需要更改的情况下,使用悲观读取才有意义。如果总是进行上述更改,那么从一开始就使用悲观_WRITE是合理的,因为它可以避免并发更新的风险


在许多情况下,使用乐观锁而不是悲观锁也是有意义的。关于在锁定策略之间进行选择的好例子和一些评论可以从以下内容中找到:

尝试安全写入锁定更改数据的伟大工作。:)但是你可能做得太过火了

  • 首先是一个小问题。不需要对persist()的调用。对于更新,只需修改从find()返回的实体的属性。entityManager会自动了解更改,并在提交期间将更改写入数据库。仅当您创建一个新对象并首次将其写入数据库(或向父关系添加一个新的子对象,并通过cascade=Persist将其级联)时,才需要Persist

  • 大多数应用程序都不太可能由具有各自独立事务和独立持久上下文的不同线程对相同数据进行“冲突”并发更新。如果这对您来说是正确的,并且您希望最大化可伸缩性,那么请使用乐观的写锁,而不是悲观的读或写锁。绝大多数web应用程序都是这样。它提供了完全相同的数据完整性、更好的性能/可伸缩性,但您必须(不经常)处理OptimisticLockException

  • 乐观写锁定是自动内置的,只需在db和实体中具有short/integer/long/TimeStamp属性,并在实体中用@Version注释它,在这种情况下,您不需要调用entityManager.lock()

如果您对上述内容感到满意,并且向实体添加了@Version属性,那么您的代码将是:

try {
   entityManager.getTransaction().begin();
   r = entityManager.find(Route.class, r.getPrimaryKey());
   r.setRoute(txtRoute.getText());
   entityManager.getTransaction().commit();
} catch (OptimisticLockException e) {
   // Logging and (maybe) some error handling here.
   // In your case you are lucky - you could simply rerun the whole method.
   // Although often automatic recovery is difficult and possibly dangerous/undesirable
   // in which case we need to report the error back to the user for manual recovery 
}
i、 e.完全没有显式锁定-实体管理器自动处理

如果您强烈需要避免并发数据更新“冲突”,并且希望代码具有有限的可伸缩性,那么可以通过悲观写锁定序列化数据访问:

try {
   entityManager.getTransaction().begin();
   r = entityManager.find(Route.class, r.getPrimaryKey(), LockModeType.PESSIMISTIC_WRITE);
   r.setRoute(txtRoute.getText());
   entityManager.getTransaction().commit();
} catch (PessimisticLockException e) {
   // log & rethrow
}
在这两种情况下,成功提交或带有自动回滚的异常意味着执行的任何锁定都将自动清除


干杯。

完美的解释和精彩的链接!非常足智多谋,谢谢!