1. 程式人生 > 程式設計 >Spring事務失效場景原理及解決方案

Spring事務失效場景原理及解決方案

1.事務失效-自身呼叫(通過REQUIRES、REQUIRES_NEW傳播屬性):自身呼叫即調該類自己的方法。

同類OrderServiceImpl 中 doSomeThing()方法 不存在事務,該方法去呼叫本類中的存在事務註解的 insertAndUpdateOrderInfo() 方法。但是insertAndUpdateOrderInfo() 其實是無法保證預想的事務性。

示列驗證:

OrderServiceImpl.insertAndUpdateOrderInfo方法中upateData(updateParam) 發生異常時,insertData(insertParam) 未發生回滾

說明:自身呼叫時候,無論是以下哪種傳播屬性均是無效的,因為自身呼叫時的子方法壓根就不會被AOP 代理攔截到以下的這兩種方式均經過驗證,無法保證子方法事務的有效性

@Transactional(propagation = Propagation.REQUIRES)
@Transactional(propagation = Propagation.REQUIRES_NEW)

@Controller
@RequestMapping("/trans")
public class TransactionalController {

  @Autowired
  OrderService orderService;

  @RequestMapping("/test.do")
  @ResponseBody
  public void getIndex(HttpServletRequest request,HttpServletResponse response,Model model) {

    orderService.doSomeThing();

  }

}

@Service
public interface OrderService {

  /*
  *新增訂單和修改其他訂單資訊
  * */
  public void doSomeThing();

}



@Service
public class OrderServiceImpl implements OrderService {
  @Autowired
  TransBusiness transBusiness;

  @Override
  public void doSomeThing() {

    insertAndUpdateOrderInfo();

  }

  @Transactional(propagation = Propagation.REQUIRED)
  public void insertAndUpdateOrderInfo(){
    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String updateTime = dateFormat.format(date);

    //步驟1:插入訂單記錄資訊
    String[] insertParam = {"555555555","977723233",updateTime,updateTime};
    transBusiness.insertData(insertParam);

    //步驟2:修改訂單記錄資訊
    String[] updateParam = {"1111111111","1"};
    transBusiness.upateData(updateParam);
  }

}



@Service
public class TransBusiness {

  @Autowired
  JdbcTemplate dalClient;

  public void insertData(String[] param) {
    Map<String,Object> resultMap = new HashMap<>();
    String sql = "INSERT INTO test_order (`order_no`,`cust_no`,create_time,update_time) VALUES (?,?,?)";

    int i = dalClient.update(sql,param);
    System.out.println("TransBusiness>>>insertData" + i);
    resultMap.put("插入的記錄數",i);
  }

  public void upateData(String[] param) {
    Map<String,Object> resultMap = new HashMap<>();
    String sql = "update test_order set order_no =?,update_time=? ? where id= ?";

    int i = dalClient.update(sql,param);
    System.out.println("TransBusiness>>>upateData" + i);
    resultMap.put("修改的記錄數",i);
  }

}

Spring事務失效場景原理及解決方案

2.1自身呼叫事務失效解決方法1—在父方法中新增事務

通過doSomeThing()方法中新增事務性,可以解決1中事務自身呼叫失效的問題。

示列驗證:

OrderServiceImpl.insertAndUpdateOrderInfo方法中當步驟1執行完成後,資料庫中並不會存在該訂單記錄。當執行步驟2時發生了異常,整個事務發生了回滾。說明才方法解決了1自身呼叫事務失效的問題。

說明:此處的@Transactional等同於 @Transactional(propagation = Propagation.REQUIRED) 表示支援當前事務,如果沒有事務就新建一個事務,這是常見的選擇,也是spring預設的事務傳播

@Override
  @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
  public void doSomeThing1() {
    insertAndUpdateOrderInfo();
  }

  public void insertAndUpdateOrderInfo(){
    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String updateTime = dateFormat.format(date);

    //步驟1:插入訂單記錄資訊
    String[] insertParam = {"8888888888",updateTime};
    transBusiness.insertData(insertParam);

    //步驟2:修改訂單記錄資訊
    String[] updateParam = {"1111111112","1"};
    transBusiness.upateData(updateParam);
  }

2.2自身呼叫事務失效解決方法2—將事務方法拆分到另外一個類中

@Service
public class TransBusiness {

  @Autowired
  JdbcTemplate dalClient;

  @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
  public void insertAndUpdateOrderInfo(){
    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String updateTime = dateFormat.format(date);

    //步驟1:插入訂單記錄資訊
    String[] insertParam = {"8888888888",updateTime};
    insertData(insertParam);

    //步驟2:修改訂單記錄資訊
    String[] updateParam = {"1111111112","1"};
    upateData(updateParam);
  }

}

3.SQL規範於1992年提出了資料庫事務隔離級別,以此用來保證併發操作資料的正確性及一致性。Mysql的事務隔離級別由低往高可分為以下幾類:

1) READ UNCOMMITTED(讀取未提交的資料)

  這是最不安全的一種級別,查詢語句在無鎖的情況下執行,就讀取到別的未提交的資料,造成髒讀,如果未提交的那個事務資料全部回滾了,而之前讀取了這個事務的資料即是髒資料,這種資料不一致性讀造成的危害是可想而知的。

2) READ COMMITTED(讀取已提交的資料)

  一個事務只能讀取資料庫中已經提交過的資料,解決了髒讀問題,但不能重複讀,即一個事務內的兩次查詢返回的資料是不一樣的。如第一次查詢金額是100,第二次去查詢可能就是50了,這就是不可重複讀取。

3) REPEATABLE READ(可重複讀取資料,這也是Mysql預設的隔離級別)

  一個事務內的兩次無鎖查詢返回的資料都是一樣的,但別的事務的新增資料也能讀取到。比如另一個事務插入了一條資料並提交,這個事務第二次去讀取的時候發現多了一條之前查詢資料列表裡面不存在的資料,這時候就是傳說的中幻讀了。這個級別避免了不可重複讀取,但不能避免幻讀的問題。

4) SERIALIZABLE(可序列化讀)

  這是效率最低最耗費資源的一個事務級別,和可重複讀類似,但在自動提交模式關閉情況下可序列化讀會給每個查詢加上共享鎖和排他鎖,意味著所有的讀操作之間不阻塞,但讀操作會阻塞別的事務的寫操作,寫操作也阻塞讀操作。

4.spring事務管理其實是對資料庫事務進行了封裝而已,並提了5種事務隔離級別和7種事務傳播機制。

4.1宣告式事務(declarative transaction management)是Spring提供的對程式事務管理的方式之一。Spring使用AOP來完成宣告式的事務管理,因而宣告式事務是以方法為單位,Spring的事務屬性自然就在於描述事務應用至方法上的策略,在Spring中事務屬性有以下引數:

Spring事務失效場景原理及解決方案

readOnly屬性的詳細理解:

1)readonly並不是所有資料庫都支援的,不同的資料庫下會有不同的結果。

2)設定了readonly後,connection都會被賦予readonly,效果取決於資料庫的實現。

a. 在oracle下測試,發現不支援readOnly,也就是不論Connection裡的readOnly屬性是true還是false均不影響SQL的增刪改查;

b. 在mysql下測試,發現支援readOnly,設定為true時,只能查詢,若增刪改會發生如下異常:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:910)
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:792)

3)在ORM中,設定了readonly會賦予一些額外的優化,例如在Hibernate中,會被禁止flush等。

4.2 spring 的 5種事務隔離級別

1) ISOLATION_DEFAULT     (使用後端資料庫預設的隔離級別)

以下四個與JDBC的隔離級別相對應:

2) ISOLATION_READ_UNCOMMITTED (允許讀取尚未提交的更改,可能導致髒讀、幻影讀或不可重複讀)

3) ISOLATION_READ_COMMITTED (允許從已經提交的併發事務讀取,可防止髒讀,但幻影讀和不可重複讀仍可能會發生)

4) ISOLATION_REPEATABLE_READ (對相同欄位的多次讀取的結果是一致的,除非資料被當前事務本身改變。可防止髒讀和不可重複讀,但幻影讀仍可能發生)

5) ISOLATION_SERIALIZABLE (完全服從ACID的隔離級別,確保不發生髒讀、不可重複讀和幻影讀。這在所有隔離級別中也是最慢的,因為它通常是通過完全鎖定當前事務所涉及的資料表來完成的)

4.3 spring的7種事務傳播機制:

1) REQUIRED(需要事務): 業務方法需要在一個事務中執行,如果方法執行時,已處在一個事務中,那麼就加入該事務,否則自己建立一個新的事務.這是spring預設的傳播行為;

2) NOT_SUPPORTED(不支援事務): 宣告方法需要事務,如果方法沒有關聯到一個事務,容器不會為它開啟事務.如果方法在一個事務中被呼叫,該事務會被掛起,在方法呼叫結束後,原先的事務便會恢復執行;

3) REQUIREDS_NEW(需要新事務):業務方法總是會為自己發起一個新的事務,如果方法已執行在一個事務中,則原有事務被掛起,新的事務被建立,直到方法結束,新事務才結束,原先的事務才會恢復執行;
備註:新建的事務如果沒有進行異常捕獲,發生異常那麼原事務方法也會發生回滾。(該結論經過自測驗證)

4) MANDATORY(強制性事務):只能在一個已存在事務中執行。業務方法不能發起自己的事務,如果業務方法在沒有事務的環境下呼叫,就拋異常

5) NEVER(不能存在事務):宣告方法絕對不能在事務範圍內執行,如果方法在某個事務範圍內執行,容器就拋異常.只有沒關聯到事務,才正常執行.

6) SUPPORTS(支援事務):如果業務方法在某個事務範圍內被呼叫,則方法成為該事務的一部分,如果業務方法在事務範圍外被呼叫,則方法在沒有事務的環境下執行.

7) NESTED(巢狀事務):如果一個活動的事務存在,則執行在一個巢狀的事務中.如果沒有活動的事務,則按REQUIRED屬性執行.它使用了一個單獨的事務,這個事務擁有多個可以回滾的保證點.內部事務回滾不會對外部事務造成影響,它只對DataSourceTransactionManager 事務管理器起效.

思考:Nested和RequiresNew的區別:

a. RequiresNew每次都建立新的獨立的物理事務,而Nested只有一個物理事務;

b. Nested巢狀事務回滾或提交不會導致外部事務回滾或提交,但外部事務回滾將導致巢狀事務回滾,而 RequiresNew由於都是全新的事務,所以之間是無關聯的;

c. Nested使用JDBC 3的儲存點實現,即如果使用低版本驅動將導致不支援巢狀事務。

實際應用中一般使用預設的事務傳播行為,偶爾會用到RequiresNew和Nested方式。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。