Spring 16: SM(Spring + MyBatis) 註解式事務 與 宣告式事務

2022-08-29 21:00:41

Spring事務處理方式

方式1:註解式事務

  • 使用@Transactional註解完成事務控制,此註解可新增到類上,則對類中所有方法執行事務的設定,註解新增到方法上,則對該方法執行事務處理

  • @Transactional(...)註解引數說明:

    • propagation = Propagation.REQUIRED:設定事務的傳播特性,例如當多個事務疊加時,誰起主導作用等
    • noRollbackForClassName = "異常名稱":指定發生什麼異常不回滾,使用的是異常的名稱
    • noRollbackFor = 異常.class:指定發生什麼異常不回滾,使用的是異常的型別
    • rollbackForClassName = "異常名稱":指定發生什麼異常必須回滾,使用的是異常的名稱
    • rollbackFor = 異常.class:指定發生什麼異常必須回滾,使用的是異常的型別:
    • timeout = -1:連線超時設定,預設值是-1,表示永不超時
    • readOnly = false:預設為false,如果是查詢操作,必須設定為true
    • isolation = Isolation.DEFAULT:使用的資料庫的預設隔離級別
  • 注意:當一個類中有較多方法時,對方法進行一對一的註解式事務管理太多繁瑣,簡單演示事務特性時可以使用註解式事務,在實際專案中不常用

方式2:宣告式事務

  • 在組態檔中新增一次,整個專案遵循該事務的設定,是Spring常用的,也是非常有名的事務處理方式

  • 要求專案中的方法命名有規範,例如:

  • 新增操作包含:add,save,insert,set等

  • 更新操作包含:update,change,modify等

  • 刪除操作包含:delete,drop,remove,clear等

  • 查詢操作包含:select,find,search,get等

  • 設定事務切面時,可以使用萬用字元來匹配滿足通配條件的方法

宣告式事務案例

applicationContext_trans.xml

  • 在src/main/resources目錄下新建applicationContext_trans.xml,注意:這裡如果使用idea預設的xml頭資訊,< tx >標籤的屬性顯示不出來,可以使用下面的頭資訊
<!-- 此組態檔和applicationContext_service.xml的功能一樣,只不過是事務設定不同 -->

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

        <!-- 匯入applicationContext_mapper.xml -->
        <import resource="applicationContext_mapper.xml"/>

        <!-- 新增包掃描 -->
        <context:component-scan base-package="com.example.service.impl"/>

        <!-- 新增事務管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>

        <!-- 設定事務切面 -->
        <tx:advice id="myadvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="*select*" read-only="true"/>
                <tx:method name="*search*" read-only="true"/>
                <tx:method name="*find*" read-only="true"/>
                <tx:method name="*get*" read-only="true"/>
                <tx:method name="*update*" propagation="REQUIRED"/>
                <tx:method name="*save*" propagation="REQUIRED"/>
                <tx:method name="*modify*" propagation="REQUIRED"/>
                <tx:method name="*set*" propagation="REQUIRED"/>
                <tx:method name="*insert*" propagation="REQUIRED"/>
                <tx:method name="*delete*" propagation="REQUIRED"/>
                <tx:method name="*remove*" propagation="REQUIRED"/>
                <tx:method name="*clear*" propagation="REQUIRED"/>
                <tx:method name="*" propagation="SUPPORTS"/>
            </tx:attributes>
        </tx:advice>

        <!-- 繫結切面和切入點 -->
        <aop:config>
            <!-- 定義切入點表示式 -->
            <aop:pointcut id="mycut" expression="execution(* com.example.service.impl.*.*(..))"/>

            <!-- 將切面和切入點表示式繫結,為目標業務實現類中的業務方法提供對應的事務切面功能 -->
            <aop:advisor advice-ref="myadvice" pointcut-ref="mycut"/>
        </aop:config>
</beans>

業務實現類

  • 修改UserServiceImpl:持有Account業務邏輯層的介面型別的變數,在User業務邏輯中巢狀呼叫Account業務
package com.example.service.impl;


import com.example.mapper.UserMapper;
import com.example.pojo.Account;
import com.example.pojo.User;
import com.example.service.AccountService;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 業務實現類
 */
@Service
public class UserServiceImpl implements UserService {
    //業務邏輯層實現類持有資料存取層的介面型別的變數
    @Autowired
    UserMapper userMapper;

    //持有Account業務邏輯層的介面型別的變數
    @Autowired
    AccountService accountService;

    @Override
    public int insert(User user) {
        int num = userMapper.insert(user);
        if(num == 1){
            System.out.println("使用者匯入成功!");
        }else{
            System.out.println("使用者匯入失敗!");
        }
        
        //巢狀呼叫賬戶的業務邏輯功能
        accountService.save(new Account(25, "荷包蛋6","富婆的賬戶6"));
        return num;
    }
}

測試

package com.example.test;

import com.example.pojo.User;
import com.example.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUserAndAccount {
    @Test
    public void testUserAndAccount(){
        //建立Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_trans.xml");
        //獲取使用者的業務邏輯層物件
        UserService userService = (UserService) ac.getBean("userServiceImpl");
        //呼叫業務功能
        userService.insert(new User(2, "荷包蛋2", "hanzhanghan2"));
    }
}

測試輸出

  • 從控制檯看出兩個業務的sql語句都執行成功,程式在出錯後終止

  • 在上述事務設定下,使用者業務和賬戶業務都被新增事務,從使用者表和賬戶表可以看出,在內部巢狀的業務執行失敗後,兩個事務都被複原,兩條記錄都未成功匯入

修改aplicationContext_trans.xml

  • 對下述兩個標籤新增新的標籤屬性,對於某些指定異常不回滾
<tx:method name="*save*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
<tx:method name="*insert*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>

測試輸出

  • 這時對於算術異常不進行事務回滾,再次測試,兩條記錄成功匯入資料表並有效儲存

注意

如果當宣告式註解所規劃的事務管理和某個業務層的業務方法對事務的個性化需求相沖突時,可以再另外開啟註解式事務並設定兩種事務的優先順序,達到優先使用註解式事務的目的。當order屬性的值越大,事務的優先順序越高

  • 在applicationContext_trans.xml中增加註解式事務驅動並設定事務優先順序
        <!-- 新增註解式事務驅動-->
        <tx:annotation-driven order="10" transaction-manager="transactionManager"/>
  • 為applicationContext_trans.xml中的< aop:advisor / >標籤設定事務級別,此時如果某業務邏輯層的業務方法使用了註解式事務,則該業務方法的事務遵循註解式事務
 <aop:advisor order="1" advice-ref="myadvice" pointcut-ref="mycut"/>

Spring事務的傳播特性

  • 多個事務之間的合併,互斥等都可以通過設定事務的傳播特性來解決
  • 常用特性:
  • PROPAGATION_REQUIRED:必被包含事務(增刪改必用)
  • PROPAGATION_REQUIRES_NEW:自己開啟新事務,不管之前是否有事務
  • PROPAGATION_SUPPORTS:支援事務,如果加入的方法有事務,則遵循該事務,如果沒有,不單開事務
  • PROPAGATION_NEVER:不能執行在事務中,如果被包在事務中,拋異常
  • PROPAGATION_NOT_SUPPORTED:不支援事務,執行在非事務環境中
  • 不常用特性:
  • PROPAGATION_MANDATORY:必須包在事務中,沒有事務則丟擲異常
  • PROPAGATION_NESTED:巢狀事務
  • 注意:
  • 事務必須宣告在業務邏輯層
  • 事務傳播特性的部分組合結果:下表列出了在User業務實現類中巢狀呼叫Account業務實現類,當內外層出現不同事務特性組合時,是分別能對users表和accounts表起到資料修改作用還是被事務回滾

Spring事務的隔離原則

  • 未提交讀:允許髒讀,可能讀到其他對談中未提交事務所修改的資料,例如,讀取資料後,發生資料回滾,則前面讀到的資料就是髒讀,讀取到了未真實提交的資料
  • 提交讀:只能讀取到已經提交的資料。oracle等多數資料庫預設都是該級別,即讀已提交(不重複讀)
  • 可重複讀:在同一個事務內的查詢都是與事務開始時刻一致,是InnoDB資料庫引擎的預設級別。在SQL標準中,該隔離級別消除了不可重複讀,但是存在幻象讀,但InnoDB解決了幻讀
  • 序列讀:完全序列化的讀,每次讀都需要獲取表級共用鎖,讀寫相互都會阻塞
  • 注意:mysql預設事務處理級別為:可重複讀。oracel支援讀已提交和序列讀兩種隔離級別,但是其預設事務隔離級別是:讀已提交

新增事務管理器的原因

  • 不同技術對事務提交和回滾的實現簡單列舉如下,可見不同的技術使用的資料庫操作物件不同

    • JDBC:Connection con.commit(); con.rollback();

    • MyBatis:SqlSession sqlSession.commit(); sqlSession.rollback();

    • Hibernate:Session session.commit(); session.rollback();

  • 使用事務管理器,目的就是為了生成相應技術下的資料庫連線 + 執行語句的物件

  • 如果使用MyBatis框架,必須使用DataSourceTransactionManager類完成處理

    <!-- 新增事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 由於事務管理必然要涉及到資料庫的操作,例如資料回滾等等,所以必須新增資料來源設定 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>