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