JPA嵌套事务和锁定

考虑这种情况,在不同的无状态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修改的同一实体,该怎么办? 这会导致死锁。是否有可能在不依赖隔离级别的情况下阻止它?

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

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

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

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

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

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

T1的读取不会获得锁定。

1a)如果T1确实冲破了变化,它获得了一个排他锁。 当T2提交时,它会尝试获取锁定但不能。 系统被阻止。 这可能是一种特殊的僵局。 完成取决于事务或锁定超时的方式。

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

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

T1读取获取共享锁。

2a)如果T1正常冲洗,它会使锁扣进入专用锁。 情况类似于1a)

2b)如果T1没有冲洗,T1保持共享锁。 当T2提交时,它会尝试获取独占锁并阻塞。 系统再次被阻止

结论:如果没有发生过早冲洗,那么乐观锁定很好,你无法严格控制。

传递实体并合并……

您可以将新实体传递给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之前保存它

如果使用上面的方法,实体将与主事务分开保存,因此如果在调用methodB()之前将其从Bean_A保存,则不会丢失任何内容:

 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 } } 

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

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

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

通过以编程方式在entityManager.persist(e1);之后提交事务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获取Transaction。 例如: 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.setName("Blah"); entityManager.persist(e1); ,也就是说,它会在事务中持续存在e1。 然后你的methodA()会调用

 methodC(); beanB.methodB();