20

考虑两种方法存在于不同无状态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 中启动。如果 methodB 需要访问由 methodA 修改的同一实体怎么办。这会导致死锁。是否可以在不依赖隔离级别的情况下防止它?

4

4 回答 4

25

嗯,让我们列出所有情况。

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

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

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

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

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

T1 的读取不会获取锁。

1a) 如果 T1 确实提前刷新了更改,但它获得了排他锁。当 T2 提交时,它会尝试获取锁但不能。系统被封锁。这可能是一种特殊的死锁。完成取决于事务或锁定如何超时。

1b) 如果 T1 没有提前刷新更改,则没有获得锁。当 T2 提交时,它会获取并释放它并成功。当 T1 尝试提交时,它会注意到冲突并失败。

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

T1 的读取获取共享锁。

2a) 如果 T1 过早刷新,它将锁变成排他锁。情况类似于 1a)

2b) 如果 T1 没有提前刷新,T1 持有共享锁。当 T2 提交时,它会尝试获取排他锁并阻塞。系统再次被封锁

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

于 2012-05-30T22:28:06.813 回答
3

传递实体并合并...

您可以将新实体传递给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

...或在调用 methodB 之前保存它

如果您使用上面的方法,实体将与您的主事务分开保存,因此如果您Bean_A在调用之前保存它,您不会丢失任何东西methodB()

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  
  }

}
于 2012-06-03T01:34:47.523 回答
1

这是最近一篇关于使用REQUIRES_NEW事务划分的文章。

根据我的经验,标准代码不应该出现死锁:带有限制性where子句和少量插入的查询。在某些特定情况下,如果在事务期间在单个表上读取或插入许多行,则某些数据库引擎可能会执行锁升级......在这种情况下,是的,可能会发生死锁。

但在这种情况下,问题不是来自 SQL 设计,REQUIRES_NEW而是来自 SQL 设计。如果无法改进该设计,那么您别无选择将隔离级别更改为更松散的级别。

于 2012-05-30T21:48:39.780 回答
0

entityManager.persist(e1); 通过在之后和之前以编程方式提交事务int age = beanB.methodB();

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

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

    // complex calc to calculate age  
  }

}

编辑CMT

如果您有 CMT,您仍然可以以编程方式提交,您只需从EJBContext. 例如: http: //geertschuring.wordpress.com/2008/10/07/how-to-use-bean-managed-transactions-with-ejb3-jpa-and-jta/

或者您可以添加一个@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void methodC()可以执行的操作e1.setName("Blah"); entityManager.persist(e1);,也就是说,它将 e1 保留在事务中。那么你methodA()会打电话

methodC();
beanB.methodB(); 
于 2012-05-24T11:52:02.963 回答