Spring 事務處理

来源:https://www.cnblogs.com/yangyuanhu/archive/2020/01/14/12194485.html
-Advertisement-
Play Games

前言: 事務處理的本質 在學習事務處理前,需要明確一點: 資料庫操作最終都要使用到JDBC,那麼無論上層如何封裝,底層都是調用Connection的commit,rollback來完成 煩人的事務處理: 在日常開發中,數據訪問層(DAO)必然需要進行事務的處理,但是我們會發現,事務處理的代碼通常是簡 ...


前言:

事務處理的本質

在學習事務處理前,需要明確一點:

資料庫操作最終都要使用到JDBC,那麼無論上層如何封裝,底層都是調用Connection的commit,rollback來完成

煩人的事務處理:

在日常開發中,數據訪問層(DAO)必然需要進行事務的處理,但是我們會發現,事務處理的代碼通常是簡單的重覆的,編寫這樣的重覆代碼會浪費大量的時間,所以我們需要找到一種方案可以將這些重覆的代碼進行抽取,以便與管理維護和復用,

我們的需求:在一系列資料庫操作上的方法上增加額外的事務處理代碼,讓原來的方法中只關註具體的數據處理,即在原本以及存在的資料庫操作方法上添加額外的事務處理邏輯

到這裡你應該想到AOP了,沒錯! 這樣的場景下AOP是最好的解決方案;

解決方案:AOP

回顧一下Spring的AOP:在結合目前的需求

1.將目標對象(DAO)放入Spring容器

2.告知Spring你的通知代碼是什麼(事務處理)

3.告知Spring 哪些方法(DAO的CRUD)要應用那些通知(不同的事務處理代碼)

4.從Spring中獲取代理對象來完成原本的CRUD,代理對象會自動完成事務處理

Spring 事務處理API

Spring作為框架,需要進行詳細的設計,全方位的考慮事務處理的各個方面,而不僅是簡單的幫你執行commit,rollback;

Spring對事務處理進行了抽象定義,形成了一套具體的API結構,如下:

image-20200114134619820

  • TransactionDefinition:定義事務的具體屬性,如隔離級別,超時設置,傳播行為等

  • TransactionStatus: 用於獲取當前事務的狀態信息

  • PlatformTransactionMananger: 主要的事務管理介面,提供三個實現類對應不同場景

類型 場景
DataSourceTransactionManager 使用Spring JDBC或 iBatis 進行持久化數據時使用
HibernateTransactionManager 使用Hibernate3.0版本 進行持久化數據時使用
JpaTransactionManager 使用JPA進行持久化時 使用
JtaTransactionManager 使用一個JTA實現來管理事務,跨數據源時使用

註意其分佈在不同的jar包中,使用時根據需要導入對應jar包

事務的傳播行為控制

這是一個新概念但是也非常簡單,即在一個執行sql的方法中調用了另一個方法時,該如何處理這兩個方法之間的事務

Spring定義了7種不同的處理方式:

常量名 含義
PROPAGATION_REQUIRED 支持當前事務。如果 A 方法已經在事 務中,則 B 事務將直接使用。否則將 創建新事務
PROPAGATION_SUPPORTS 支持當前事務。如果 A 方法已經在事 務中,則 B 事務將直接使用。否則將 以非事務狀態執行
PROPAGATION_MANDATORY 支持當前事務。如果 A 方法沒有事 務,則拋出異常
PROPAGATION_REQUIRES_NEW 將創建新的事務,如果 A 方法已經在 事務中,則將 A 事務掛起
PROPAGATION_NOT_SUPPORTED 不支持當前事務,總是以非事務狀態 執行。如果 A 方法已經在事務中,則 將其掛起
PROPAGATION_NEVER 不支持當前事務,如果 A 方法在事務 中,則拋出異常
PROPAGATION.NESTED 嵌套事務,當外層出現異常則連同內層一起回滾,若外層正常而內部異常,僅回滾內部操作

上述涉及的掛起,意思是開啟一個獨立的事務,已存在的事務暫停執行,等待新事務執行完畢後繼續執行,兩個事務不會互相影響

Spring 整合MyBatis

在開始前我們先完成一個基礎的CURD功能,後續開發中Spring + MyBatis項目是很常見的,那要將MyBatis整合到Spring中來,要明確一下兩者的關係和定位

  • Spring Java開發框架,其本質是一個對象容器,可以幫助我們完成IOC,DI,AOP

  • MyBatis是一個持久層框架,用於簡化對資料庫的操作

將兩者整合起來,就是將MyBatis中的對象交給Spring來管理,且將這些對象的依賴也交給Spring來管理;

添加依賴:

Spring 3.0 的開發在 MyBatis 3.0 官方發佈前就結束了,於是MyBatis社區自己召集開發者完成了這一部分工作,於是有了mybatis-spring項目,後續Spring也就沒有必要在開發一個新的模塊了,所以該jar是MyBatis提供的

<!-- Spring整合MyBatis依賴 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.3</version>
</dependency>

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.2</version>
</dependency>



<!--JDBC-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.44</version>
</dependency>

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
</dependency>


<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>

<!--Spring JDBC-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>
<!--事務管理-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>
<!--AspectJ-->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.0</version>
</dependency>

SM基礎使用

配置文件

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

<!--    載入properties-->
    <context:property-placeholder location="jdbc.properties"/>

<!--    數據源 後續可更換為其他更方便的數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${url}"/>
        <property name="username" value="${usr}"/>
        <property name="password" value="${password}"/>
        <property name="driverClassName" value="${driver}"/>
    </bean>
  
<!--    MyBatis核心對象SqlSessionFactory-->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    掃描Mapper 將代理對象放入Spring-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.yh.dao"/>
    </bean>
</beans>

jdbc.properties:

driver = com.mysql.jdbc.Driver
url = jdbc:mysql:///SMDB?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
usr = root
password = admin
location = /Users/jerry/.m2/repository/mysql/mysql-connector-java/8.0.17/mysql-connector-java-8.0.17.jar

測試代碼:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

public class Test1 {
    @Autowired
    private StudentMapper studentMapper;
    @Test
    public  void test(){
        Student student = studentMapper.selectByPrimaryKey(1);
        System.out.println(student);
    }
}

編碼式事務

編碼式事務,即在源代碼中加入 事務處理的代碼, 即commit,rollback等,這是非常原始的做法僅作為瞭解

純手動管理事務

配置文件:

<!--    在之前的配置中添加內容-->

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

<!--    事務定義 -->
    <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
<!--        隔離級別 可預設-->
        <property name="isolationLevelName" value="ISOLATION_REPEATABLE_READ"/>
<!--        傳播行為 可預設-->
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
    </bean>

測試代碼:

@Autowired
private StudentMapper studentMapper;
@Autowired
private DataSourceTransactionManager manager;
@Autowired
private DefaultTransactionDefinition definition;

@Test
public  void test(){
    TransactionStatus transactionStatus = manager.getTransaction(definition);
    try{
        Student student = studentMapper.selectByPrimaryKey(1);
        System.out.println(student);
        student.setAge(201);
        studentMapper.updateByPrimaryKey(student);
      
        int i = 1/0;
        manager.commit(transactionStatus);
    }catch (Exception e){
        e.printStackTrace();
        manager.rollback(transactionStatus);
    }
}

上述代碼僅用於測試事務處理的有效性;

我們已經在Spring中配置了MyBatis,併進行了事務處理,但是沒有解決重覆代碼的問題

使用事務模板

事務模板原理是將要執行的具體代碼交給模板,模板會在執行這寫代碼的同時處理事務,當這寫代碼出現異常時則自動回滾事務,以此來簡化書寫

配置文件:

<!-- 在上述配置基礎上刪除事務定義 添加模板Bean-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
  <!--       傳播行為隔離級別等參數  可預設-->
  <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
  <property name="transactionManager" ref="transactionManager"/>
</bean>

測試代碼:

public class Test2 {

    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private TransactionTemplate transactionTemplate;


    @Test
    public  void test(){
        transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus transactionStatus) {
                Student student = studentMapper.selectByPrimaryKey(1);
                System.out.println(student);
                student.setAge(1101);
                studentMapper.updateByPrimaryKey(student);
//                int i = 1/0;
                return null;
            }
        });
    }
}

可以看到事務模板要求提供一個實現類來提交原始的資料庫操作給模板,從而完成事務代碼的增強

無論是純手動管理還是利用模板,依然存在大量與業務無關的重覆代碼,這也是編碼式事務最大的問題;

聲明式事務

即不需要在原來的業務邏輯代碼中加入任何事務相關的代碼,而是通過xml,或者註解的方式,來告訴框架,哪些方法需要添加事務處理代碼,讓框架來完成在原始業務邏輯前後增加事務處理的代碼(通過AOP),這也是AOP使用較多的場景之一;

基於tx名稱空間的配置

配置文件:

需要引入aop和tx名稱空間

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

    <import resource="mybatis-beans.xml"/>
    <context:component-scan base-package="com.yh.service"/>

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

<!--    事務通知-->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
<!--            name指定要應用的方法名稱 還有其他事務常用屬性如隔離級別傳播行為等..-->
            <tx:method name="*" read-only="false"/>
        </tx:attributes>
    </tx:advice>

<!--    切點信息-->
    <aop:config >
<!--        根據表達式中的信息可以自動查找到目標對象 從而進行增強 先查找目標再生產代理-->
        <aop:pointcut id="pointcut" expression="execution(* com.yh.service.*.*(..))"/>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

tx:method屬性:

屬性名 含義
name 匹配的方法名稱
isolation 事務隔離級別
read-only 是否採用優化的只 讀事務
timeout 超時
rollback-for 需要回滾的異常類
propagation 傳播行為
no-rollback-for 不需要回滾的異常類

Service:

@Service
public class StudentService {
    @Autowired
    private StudentMapper studentMapper;
    public Student getStudent(int id ){
        return studentMapper.selectByPrimaryKey(id);
    }
    public void update(Student student){
        studentMapper.updateByPrimaryKey(student);
        int i  = 1/0;
    }
}

測試代碼:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class Test3 {
    @Autowired
    StudentService studentService;

    @Test
    public void test(){
        Student student = studentService.getStudent(1);
        System.out.println(student);
        student.setAge(8818);
        studentService.update(student);
    }
}

強調:事務增強應該應用到Service層,即業務邏輯層,應為一個業務方法可能涉及多個資料庫操作,當某個操作遇到異常時需要將所有操作全部回滾

基於註解的配置

Spring當然也支持採用註解形式來處理事務

開啟註解事務支持:

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

    <!--為了分離關註點,故將MyBatis相關配置放到其他配置文件了-->
    <import resource="mybatis-beans.xml"/>
    <context:component-scan base-package="com.yh.service"/>
  
<!--    添加事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    開啟註解事務管理-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

Service中增加方法:

@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transactionTest(){
    Student student = getStudent(1);
    student.setAge(1);
    update(student);
    int i = 1/0;
    student.setName("jack");
    update(student);
}
//當然註解上的參數都是可選的採用預設值即可

測試代碼

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class Test4 {

    @Autowired
    StudentService studentService;

    @Test
    public void test(){
        studentService.transactionTest();
    }
}

你可能會覺得註解的方式比xml配置簡單的多,但是考慮一下,當你的項目特別大,涉及的表很多的時候呢,你可能需要些很多很多的註解,假設後期需要修改某些屬性,還得一個個改;

所以大項目建議採用XML,小項目使用註解也ok;

原理簡述

聲明式事務其底層用的還是AOP,你完全可以自己手動的配置每個環節,如目標,通知,切麵,代理等,這能讓你更清晰的理解每一行代碼背後到底做了什麼事情;

配置文件:

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

    <import resource="mybatis-beans.xml"/>

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

<!--    要進行事務增強的目標對象-->
    <bean id="serviceTarget" class="com.yh.service.StudentService"/>
<!--    事務通知-->
    <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
<!--    代理對象-->
    <bean id="orderService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="serviceTarget"/>
        <property name="interceptorNames">
            <list>
                <idref bean="transactionInterceptor"/>
            </list>
        </property>
    </bean>
</beans>

測試代碼:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext5.xml")
public class Test5 {

    @Autowired
    @Qualifier("orderService")
    StudentService studentService;

    @Test
    public void test(){
        Student student = studentService.getStudent(1);
        student.setAge(1);
        studentService.update(student);
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 小程式開發成本低,一個小程式,可同時運行在iOS和Android系統上;開發周期短,技術學習成本低,獲取用戶成本低,安裝下載成本低;運營成本低,畢竟無論是微信還是支付寶等,都有大量的用戶所在。總而言之,船小好調頭,試錯成本小。 ...
  • 有的時候需要測試下web項目中post、get請求是否正確,但是這個時候電腦上沒有安裝測試工具,怎麼辦呢?直接用瀏覽器控制台測試,打開網站,F12控制台,在控制臺下複製粘貼下麵的ajax請求,之後ajax請求。 第一步:控制台寫方法 第二步:控制台調用 url:請求的URL,method:post/ ...
  • 1.前言 我們經常使用 cron 表達式來定義定時任務的執行策略,今天我們就總結一下 cron 表達式的一些相關知識。 2. cron 表達式的定義 cron 表達式是一個字元串,該字元串由 個空格分為 個域,每一個域代表一個時間含義。 格式如下: 通常定義 “年” 的部分可以省略,實際常用的由 前 ...
  • 前段時間參加了IAS2019(互聯網架構峰會),本次峰會以中台為主題,所以又稱中台戰略大會,據說是全國首屆關於中台戰略的會議,會議上有許多優秀的企業架構師帶來了他們各自在實踐中台過程中的心得。本文就筆者對自己參與的會場的情況做一些分享,同時也寫寫自己參會以及查閱相關資料後關於中台這一概念的理解和體會 ...
  • 微服務可以幫我們成就更大的夢想,為什麼呢?請看老兵哥近些年推廣微服務架構過程中收穫的心得體會! ...
  • 一、資料庫中時間類型 1.三種時間類型:DateTimeField、DataField、TimeField三種類型;在使用之前需先導入import django.utils.timezone包 2.該三種類型分別對應這Datetime、Data、Time三種對象; 3.時間類型,三個屬性,auto_ ...
  • 一、定義 是`JDK ThreadLocal`意思就是本地線程的意思。 1.1 是什麼? 要想知道他是個啥,我們看看 的源碼(基於 )中對這個類的介紹: 大致能夠總結出: 1. 可以給我們提供一個線程內的局部變數,而且這個變數與一般的變數還不同,它是每個線程獨有的,與其他線程互不幹擾的; 1. 與普 ...
  • 寫在前面 考研在即,想多瞭解考研er的想法,就是去找學長學姐或者去網上搜索,貼吧就是一個好地方。而藉助強大的工具可以快速從網路魚龍混雜的信息中得到有價值的信息。雖然網上有很多爬取百度貼吧的教程和例子,但是貼吧規則更新快,目的不一樣,爬取的內容也不一樣,所以就有了這個工具。 目的 爬取1000條帖子→ ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...