【问题标题】:Entity classes and Record locking实体类和记录锁定
【发布时间】:2012-06-01 15:46:45
【问题描述】:

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

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

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

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

Exception Description: No transaction is currently active

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

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

谢谢!

【问题讨论】:

    标签: java locking entity entitymanager


    【解决方案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 是合理的,因为它可以避免并发更新的风险。

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

    【讨论】:

    • 完美的解释和一个很好的链接!非常足智多谋,谢谢!
    【解决方案2】:

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

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

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

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

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

    干杯。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-04
      • 1970-01-01
      相关资源
      最近更新 更多