Small case of Spring transaction propagation problem

A small problem encountered at work was recorded

The situation is this:

  There is an interface for sending red envelopes written in CouponService. All red envelopes sent regardless of the scenario will eventually call the red envelope receiving interface in this Service. 

    And add a transaction label to indicate that it is managed by a transaction  
  @Transactional(rollbackFor=Exception.class)
  List<CouponVo> checkRepertoryAndSend(参数....)

Then there is a scenario where the interface of sending red packets or something in batches is written in CouponFacadeImpl;
    This interface is also a thing; there are some other operations and verifications here, if they do not pass, they will all be rolled back, so this is also defined as a transaction (this is not the point) 

  @Transactional(rollbackFor=Exception.class)
   CouponFacade.sendCpByQuene(参数....) {
	//...
	for(int i = 0;i<telphones.length;i++) {
    try {
       //omit....
        couponService.checkRepeatable(CouponTempOrNotEnum.NORMAL, userId.toString(), templateId,
                YesOrNo.YES.getVal().equals(isRepeate)?"n":cpTemplate.getRepeatable(), null);
        //The form of the text box does not need to be sent in batches; because it is difficult to avoid repeated sending by the same user at the same time in batches
       if(text0file1.intValue()==0){//If this kind of incident occurs, one inventory will be reduced
            couponService.checkRepertoryAndSend(CouponTempOrNotEnum.NORMAL,userId+"",cpTemplate,"system",
            null);
           successNum.getAndIncrement();
       }else{
        couponService.getInsertCouponList(errTel,telphones[i],userId,coupons,couponFlows,cpTemplate,err,successNum);
       }
    } catch (BusinessException e) {
        errTel.append(telphones[i]).append(",");
        if(e.getCode().equals("2003")){//User does not exist
            err.append("1,").append(telphones[i]).append("|");
        }else if(e.getCode().equals("10000")){//Received
            err.append("2,").append(telphones[i]).append("|");
        }else if(e.getCode().equals("10003")){//Insufficient stock
            err.append("3,").append(telphones[i]).append("|");
        }else{//Other exceptions
            err.append("-1,").append(telphones[i]).append(":").append("|");
        }
    }
	//...
}


This is a batch sending red packet interface which has a loop calling a single sending interface
checkRepertoryAndSend
   (There is a special batch sending in batches. The batch mentioned here is a small amount of red envelopes sent, and the sending interface is called directly in a loop. This is just to explain the problem.) 

Expect the result : when a business exception is thrown in checkRepertoryAndSend() (such as insufficient inventory), the transaction needs to be rolled back;
                  But in the sendCpByQuene() method, catch this exception, record the reason for the failure, and then continue to the next one; 

Final execution result :
    SendCpByQuene is also rolled back, and the two transactions are rolled back together; 

Finding the Problem : Propagation and Isolation Levels of Spring Transactions
Understand the principle :
Spring complements and extends the isolation level of JDBC, and provides seven transaction propagation behaviors.
1. PROPAGATION_REQUIRED: The default transaction type, if not, create a new transaction; if there is, join the current transaction. Suitable for most situations.
2. PROPAGATION_REQUIRES_NEW: If not, create a new transaction; if there is, suspend the current transaction.
 3. PROPAGATION_NESTED: If not, create a new transaction; if there is, nest other transactions in the current transaction.
4. PROPAGATION_SUPPORTS: If not, execute it in a non-transactional manner; if so, use the current transaction.
5. PROPAGATION_NOT_SUPPORTED: If not, execute it in a non-transactional manner; if so, suspend the current transaction. i.e. transactions are not supported anyway.
6. PROPAGATION_NEVER: If not, it will be executed in a non-transactional manner; if there is, an exception will be thrown.
7. PROPAGATION_MANDATORY: If not, throw an exception; if there is, use the current transaction.

Look at the first default transaction type. In our case, the first method, sendCpByQuene, creates a new transaction. The second method, checkRepertoryAndSend, finds that a transaction already exists, and directly joins the transaction created by the first method, sendCpByQuene. bingo 
Knowing the problem, so the solution is to keep the two not in one transaction, but in two separate transactions.
After looking at the above, only PROPAGATION_NESTED meets the requirements;

solution:
So we just change the transaction level of the second method to PROPAGATION_NESTED
@Transactional(rollbackFor=Exception.class,propagation= Propagation.NESTED)

Note: The first method must be caught, otherwise it will roll back if thrown up.

Extension: What if one of these two transaction methods is not in two classes, but in one class?
  Result: In the same class, the transaction in the called transaction method will be invalidated;

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325894939&siteId=291194637