JPA 嵌套事务和锁定

2022-09-02 09:51:15

考虑两种方法存在于不同无状态Bean中的场景

public class Bean_A {
   Bean_B beanB; // Injected or whatever
   public void methodA() {
    Entity e1 = // get from db
    e1.setName("Blah");
    entityManager.persist(e1);
    int age = beanB.methodB();

   }
} 
public class Bean_B {
  //Note transaction
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   public void methodB() {

    // complex calc to calculate age  
  }

}

由 BeanA.methodA 启动的事务将被暂停,新事务将在 BeanB.methodB 中启动。如果方法 B 需要访问由方法 A 修改的同一实体,该怎么办?这将导致死锁。是否可以在不依赖隔离级别的情况下防止它?


答案 1

嗯,让我们列出所有案例。

REQUIRES_NEW并不真正嵌套事务,但正如您所提到的,暂停了当前事务。然后,只有两个交易访问相同的信息。(这类似于两个常规并发事务,不同之处在于它们不是并发的,而是位于同一执行线程中)。

T1 T2          T1 T2
―              ―
|              |
               |
   ―           |  ―
   |           |  |
   |     =     |  |
   ―           |  ―
               |
|              |
―              ―

然后,我们需要考虑乐观悲观锁定。

此外,我们需要考虑由 ORM 操作的冲洗。使用ORM,当写入发生时,我们没有明确的控制,因为它是由框架控制的。通常,在提交之前会发生一次隐式刷新,但如果修改了许多条目,则框架也可以执行中间刷新。flush

1) 让我们考虑乐观锁,其中读取不获取锁,但写入获取独占锁。

T1 的读取不会获取锁。

1a)如果T1确实在成熟时刷新了更改,那么它就会获得一个独占锁。当 T2 提交时,它会尝试获取锁,但无法获取。系统被阻止。这可能是一种特定类型的死锁。完成取决于事务或锁定超时的方式。

1b) 如果 T1 未预先刷新更改,则未获取任何锁。当 T2 提交时,它会获取并释放它,并且是成功的。当 T1 尝试提交时,它会注意到冲突并失败。

2)让我们考虑悲观锁定,其中读取获取共享锁并写入独占锁。

T1 的读取获取共享锁。

2a) 如果 T1 过早刷新,则会将锁 inta 变成独占锁。情况与1a相似)

2b) 如果 T1 未过早刷新,则 T1 持有共享锁。当 T2 提交时,它会尝试获取独占锁和块。系统再次被阻止

结论:如果没有发生过早的冲洗,乐观锁定是可以的,您无法严格控制。


答案 2

传递实体并合并...

您可以将新实体传递给 ,然后将其合并到 new .当方法返回时,请刷新实体以查看更改:methodB()EntityManager

public class Bean_A {
  Bean_B beanB; // Injected or whatever
  public void methodA() {
    Entity e1 = // get from db
    e1.setName("Blah");
    entityManager.persist(e1);
    int age = beanB.methodB(e1);
    entityManager.refresh(e1);
  }
} 

public class Bean_B {
  //Note transaction
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void methodB(Entity e1) {
    e1 = entityManager.merge(e1);
    // complex calc to calculate age  
  }

}

请注意,这将在 之后新事务关闭时提交您的实体。methodB

...或在调用方法 B 之前保存它

如果您使用上述方法,则实体将与主事务分开保存,因此,如果您在调用之前保存它,则不会丢失任何内容:Bean_AmethodB()

public class Bean_A {
  Bean_B beanB; // Injected or whatever

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void createEntity() {
    Entity e1 = // get from db
    e1.setName("Blah");
    entityManager.persist(e1);
  }

  public void methodA() {
    createEntity()   
    int age = beanB.methodB();
  }
} 

public class Bean_B {
  //Note transaction
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void methodB() {

    // complex calc to calculate age  
  }

}

推荐