JPA Nested Transactions And Locking

你说的曾经没有我的故事 提交于 2019-12-02 19:20:43
ewernli

Hm, let's list all cases.

REQUIRES_NEW does not truely nest transactions, but as you mentioned pauses the current one. There are then simply two transactions accessing the same information. (This is similarly to two regular concurrent transactions, except that they are not concurrent but in the same thread of execution).

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

Then we need to consider optimistic vs. pessimistic locking.

Also, we need to consider flushes operated by ORMs. With ORMs, we do not have a clear control when writes occurs, since flush is controlled by the framework. Usually, one implicit flush happens before the commit, but if many entries are modified, the framework can do intermediate flushes as well.

1) Let's consider optimistic locking, where read do not acquire locks, but write acquire exclusive locks.

The read by T1 does not acquire a lock.

1a) If T1 did flush the changes prematurly, it acquired a exclusive lock though. When T2 commits, it attempts to acquire the lock but can't. The system is blocked. This can be though of a particular kind of deadlock. Completion depends on how transactions or locks time out.

1b) If T1 did not flush the changes prematurly, no lock has been acquired. When T2 commits, it acquires and releases it and is sucessful. When T1 attempt to commit, it notices a conflict and fails.

2) Let's consider pessimistic locking, where read acquire shared locks and write exclusive locks.

The read by T1 acquire a shared lock.

2a) If T1 flushed prematurly, it turnes the lock inta an exclusive lock. The situation is similar as 1a)

2b) If T1 did not flush prematurly, T1 holds a shared lock. When T2 commits, it attempts to acquire an exclusive lock and blocks. The system is blocked again.

Conclusion: it's fine with optimistic locking if no premature flushes happen, which you can not stricly control.

Pass the entity and merge...

You can pass your new entity to methodB(), and merge it to the new EntityManager. When the method returns refresh your entity to see the changes:

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  
  }

}

Note that this will commit your entity when the new transaction closes after methodB.

...or save it before calling methodB

If you use the method above the entity is saved separately from your main transaction, so you don't loose anything if you save it from Bean_A before calling 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  
  }

}

Here is a recent article about the use of REQUIRES_NEW transaction demarcation.

From my experience, there should be no dead-lock with standard code: queries with restrictive where clause and few inserts. In some specific cases, some database engine may do lock escalation if there is many rows read or inserted on a single table during the transaction... and in that case, yes a dead-lock may occur.

But in that case, the problem does not come from REQUIRES_NEW but from SQL design. If that design cannot be improved, then you have no other choice to change isolation level to a more loose level.

by programatically committing the transaction after entityManager.persist(e1); and before 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  
  }

}

EDIT: CMT

If you have CMT, you can still commit programatically, you just get the Transaction from the EJBContext. e.g.: http://geertschuring.wordpress.com/2008/10/07/how-to-use-bean-managed-transactions-with-ejb3-jpa-and-jta/

or you can add a @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void methodC() which would do the e1.setName("Blah"); entityManager.persist(e1);, that is, it would persist e1 in a transaction. then your methodA() would call

methodC();
beanB.methodB(); 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!