Spring 16: SM(Spring + MyBatis) 註解式事務 與 聲明式事務

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="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xmlns:context="//www.springframework.org/schema/context"
       xmlns:aop="//www.springframework.org/schema/aop"
       xmlns:tx="//www.springframework.org/schema/tx"
       xsi:schemaLocation="//www.springframework.org/schema/aop //www.springframework.org/schema/aop/spring-aop-4.3.xsd
		//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans-3.2.xsd
		//www.springframework.org/schema/tx //www.springframework.org/schema/tx/spring-tx-4.3.xsd
		//www.springframework.org/schema/context //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語句都執行成功,程式在出錯後終止

image

  • 在上述事務配置下,用戶業務和賬戶業務都被添加事務,從用戶表和賬戶表可以看出,在內部嵌套的業務執行失敗後,兩個事務都被撤銷,兩條記錄都未成功導入

image

修改aplicationContext_trans.xml

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

測試輸出

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

image

注意

如果當聲明式註解所規劃的事務管理和某個業務層的業務方法對事務的個性化需求相衝突時,可以再另外開啟註解式事務並設置兩種事務的優先順序,達到優先使用註解式事務的目的。當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表起到數據修改作用還是被事務回滾

image

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>