3

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

entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getPrimaryKey());
r.setRoute(txtRoute.getText());
entityManager.persist(r);
entityManager.getTransaction().commit();

根据我的反复试验,看来我需要 WWEntityManager.entityManager.lock(r, LockModeType.PESSIMISTIC_READ);.begin().

我自然认为我会WWEntityManager.entityManager.lock(r, LockModeType.NONE);在提交后使用,但它给了我这个:

Exception Description: No transaction is currently active

我还没有尝试将它放在提交之前,但这不会破坏锁定记录的目的,因为我的目标是避免在 50 个用户尝试一次提交更改的情况下冲突记录?

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

谢谢你!

4

2 回答 2

1

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

可能是您使用悲观锁定的目的不是它们的真正用途。如果我的假设是错误的,那么您可以忽略答案的结尾。当您的事务在实体(行)上持有悲观读锁时,可以保证以下内容:

  • 无脏读:其他事务看不到您对锁定行执行的操作的结果。
  • 可重复读取:没有来自其他事务的修改
  • 如果您的事务修改了锁定的实体,则 PESSIMISTIC_READ 将升级为 PESSIMISTIC_WRITE 或者如果无法升级锁定,则事务将失败。

下面粗略地描述在事务开始时获得锁定的场景:

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();

通常,数据库没有单独支持悲观读取,因此您实际上是从 PESSIMISTIC_READ 开始锁定行。仅当预期不会更改锁定行时,使用 PESSIMISTIC_READ 才有意义。如果上述更改总是进行,那么从一开始就使用 PESSIMISTIC_WRITE 是合理的,因为它可以避免并发更新的风险。

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

于 2012-06-01T19:26:03.510 回答
0

伟大的工作试图安全地写入锁定您的更改数据。:) 但你可能会走火入魔/做很长的路要走。

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

  • 大多数应用程序通过具有自己的单独事务和单独的持久上下文的不同线程对相同数据的“冲突”并发更新的可能性很低。如果这对您来说是正确的,并且您希望最大限度地提高可伸缩性,那么请使用乐观的写入锁,而不是悲观的读取或写入锁。绝大多数 Web 应用程序都是这种情况。它提供完全相同的数据完整性、更好的性能/可扩展性,但您必须(不经常)处理 OptimisticLockException。

  • 乐观写锁定是自动内置的,只需在数据库和实体中具有一个短/整数/长/时间戳属性并在实体中使用@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 
}

即根本没有显式锁定- 实体管理器自动处理它。

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

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
}

在这两种情况下,成功提交或具有自动回滚的异常意味着执行的任何锁定都将自动清除。

干杯。

于 2012-10-13T14:49:15.230 回答