1. 程式人生 > >【Spring Boot學習總結】13.Spring Boot事務控制

【Spring Boot學習總結】13.Spring Boot事務控制

上一篇我們講解了Spring Boot與MyBatis的結合開發,併成功操作了資料庫。眾所周知,保證資料庫一致性的操作,就是事務的控制。 而Spring事務管理可以分為兩種:程式設計式以及宣告式。 其中程式設計式事務就是使用編寫程式碼的方式,進行事務的控制。而宣告式事務一般通過切面程式設計(AOP)的方式,注入到要操作的邏輯的前後,將業務邏輯與事務處理邏輯解耦。 由於使用宣告式事務可以保證業務程式碼邏輯不會受到事務邏輯的汙染, 所以在實際的工程中使用宣告式事務比較多。 對於宣告式事務的實現,在Java工程中一般有有兩種方式: (1)使用配置檔案(XML)進行事務規則相關規則的宣告 (2)使用@Transactional註解進行控制 這裡我們著重講解傳統工程與Spring Boot進行宣告式事務控制的不同。

一、傳統工程與Spring Boot對事務的配置處理

在傳統的Web工程中,我們通常使用XML配置,利用Spring的AOP切面程式設計手段,將事務以切面的方式注入到Service的各個資料庫操作方法中去:

<!-- dataSource資料來源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    
    <!-- 連線池中保留的最大連線數。預設為15 -->
    <property name="maxPoolSize" value="${c3p0.pool.maxPoolSize}"/>
    <!-- 連線池中保留的最小連線數。預設為15 -->
    <property name="minPoolSize" value="${c3p0.pool.minPoolSize}" />
    <!-- 初始化時建立的連線數,應在minPoolSize與maxPoolSize之間取值。預設為3 -->
    <property name="initialPoolSize" value="${c3p0.pool.initialPoolSize}"/>
    <!-- 定義在從資料庫獲取新連線失敗後重復嘗試獲取的次數,預設為30 -->
    <property name="acquireIncrement" value="${c3p0.pool.acquireIncrement}"/>
</bean>

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

<!-- 事務通知(隔離級別、傳播行為) -->
<tx:advice id="txAdivce" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="insert*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="save*" propagation="REQUIRED"/>
        
        <tx:method name="find*" read-only="false"/>
        <tx:method name="get*" read-only="false"/>
        <tx:method name="view*" read-only="false"/>
    </tx:attributes>
</tx:advice>

<!-- 切入事務 -->
<aop:config>
    <aop:pointcut expression="execution(* com.*.service.*.*(..))" id="txPointcut"/>
    <aop:advisor advice-ref="txAdivce" pointcut-ref="txPointcut"/>
</aop:config>

可以看到,針對事務,我們首先配置了【資料來源】,然後配置了【事務管理器】,然後配置了【事務通知】,定義了各種方法的事務操作規範。最後將【事務管理器】切入需要進行事務管理的Service方法中。而在Spring Boot中的推薦操作是,使用@Transactional註解來申明事務。 要在Spring boot中支援事務,首先要匯入Spring boot提供的JDBC或JPA依賴:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
   <scope>test</scope>
</dependency>

當我們使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依賴的時候,Spring Boot會自動預設分別注入DataSourceTransactionManager或JpaTransactionManager, 並進行一系列的事務初始化操作,所以我們不需要任何額外配置就可以用@Transactional註解進行事務的使用。 雖然在傳統的工程中也可以使用@Transactional註解來申明事務,但是還是需要使用XML來配置事務管理器(DataSourceTransactionManager)。

那麼大家可能會有一個疑問,因為傳統工程中使用XML配置事務時,需要給DataSourceTransactionManager事務管理器配置資料來源DataSource,那麼Spring Boot進行自動配置的話, Spring Boot在注入DataSourceTransactionManager事務管理器時,是如何找到我們配置的DataSource資料來源的呢? 答案是Spring Boot會自動到Spring容器中尋找我們配置好的DataSource。也即是之前我們的手動操作,現在使用Spring Boot變成了自動化操作。

二、@Transactional的使用

@Transactional不僅可以註解在方法上,也可以註解在類上。當註解在類上的時候意味著此類的所有public方法都是開啟事務的。如果類級別和方法級別同時使用了@Transactional註解,則 使用在類級別的註解會過載方法級別的註解。

使用@Transactional註解進行事務控制時,可以在其中新增有關“隔離級別”和“傳播行為”的指定:(1)隔離級別DEFAULT :這是預設值,表示使用底層資料庫的預設隔離級別。對大部分資料庫而言,通常這值就是: READ_COMMITTED 。READ_UNCOMMITTED :該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的資料。該級別不能防止髒讀和不可重複讀,因此很少使用該隔離級別。READ_COMMITTED :該隔離級別表示一個事務只能讀取另一個事務已經提交的資料。該級別可以防止髒讀,這也是大多數情況下的推薦值。REPEATABLE_READ :該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的資料滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止髒讀和不可重複讀。SERIALIZABLE :所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。 指定方法:通過使用 isolation 屬性設定,例如: @Transactional(isolation = Isolation.DEFAULT)

(2)傳播行為REQUIRED :如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。SUPPORTS :如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。MANDATORY :如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。REQUIRES_NEW :建立一個新的事務,如果當前存在事務,則把當前事務掛起。NOT_SUPPORTED :以非事務方式執行,如果當前存在事務,則把當前事務掛起。NEVER :以非事務方式執行,如果當前存在事務,則丟擲異常。NESTED :如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於 REQUIRED 。 指定方法:通過使用 propagation 屬性設定,例如: @Transactional(propagation = Propagation.REQUIRED)

在Spring Boot中使用@Transactional註解,只需要在啟動類上新增@EnableTransactionManagement註解開啟事務支援:

package cn.com.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement //開啟宣告式事務
@SpringBootApplication
//Sprnig Boot專案的核心註解,主要目的是開啟自動配置
public class MainApplication {
    //該main方法作為專案啟動的入口
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

然後在訪問資料庫的Service方法上添加註解@Transactional註解即可:

package cn.com.springboot.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.com.springboot.mapper.UserMapper;
import cn.com.springboot.pojo.User;
import cn.com.springboot.service.UserService;

@Service("userService")
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;
    
    @Override
    @Transactional
    public User findUserById(int id) {
        return userMapper.findUserById(id);
    }

}

當然,如果我們想要使用自定義的事務管理器,可以在配置類中設定自定義事務管理器,並以@Bean暴露給Spring容器:

package cn.com.springboot.data;
import javax.annotation.Resource;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration
public class TransactionalConfiguration implements TransactionManagementConfigurer{

    @Resource(name="txManager1")
    private PlatformTransactionManager txManager1;
    
    // 建立事務管理器1
    @Bean(name = "txManager1")
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    // 建立事務管理器2
    @Bean(name = "txManager2")
    public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }
    
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return txManager1;
    }
}

這裡配置類實現了TransactionManagementConfigurer介面,其必須實現annotationDrivenTransactionManager()方法,該方法的返回值代表在擁有多個事務管理器的情況下預設使用的事務管理器。 然後在@Transactional註解中使用value指定需要的事務管理器的名稱即可(不指定的話預設使用annotationDrivenTransactionManager()方法的返回值):

@Override
// 使用value具體指定使用哪個事務管理器
@Transactional(value="txManager1")
public User findUserById(int id) {
    return userMapper.findUserById(id);
}

@Override
// 沒有指定value,則預設使用方法 annotationDrivenTransactionManager() 返回的事務管理器
@Transactional
public User findUserById2(int id) {
    return userMapper.findUserById(id);
}

三、@Transactional註解實現原理剖析

使用@Transactional註解對某目標方法進行標註時,Spring會使用AOP代理,生成一個代理物件,該物件會根據@Transactional註解的屬性配置資訊,來決定是否使用TransactionInterceptor攔截器來進行 攔截。如果該方法需要使用事務控制,則需要使用TransactionInterceptor事務攔截器,對該方法進行攔截,在該目標方法執行之前建立並開啟事務,然後執行目標方法,最後在目標方法執行完畢後, 根據執行情況是否出現異常,利用抽象事務管理器AbstractPlatformTransactionManager操作資料來源DataSource提交或回滾事務: Spring AOP代理類有兩種:(1)CglibAopProxy 垃圾回收類庫提供的代理類。 上圖就是以CglibAopProxy為例,需要呼叫其內部類的 DynamicAdvisedInterceptor 的 intercept 方法來進行代理。

(2)JdkDynamicAopProxy JDK提供的代理類。 需要呼叫其 invoke 方法來進行代理。

事務管理的框架是由抽象事務管理器AbstractPlatformTransactionManager來提供的,而具體的底層事務處理實現,由PlatformTransactionManager的具體實現類來實現,如事務管理器 DataSourceTransactionManager。 不同的事務管理器管理不同的資料資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。 PlatformTransactionManager,AbstractPlatformTransactionManager 及具體實現類關係下圖所示:

參考: 《JavaEE 開發的顛覆者 Spring Boot實戰》 汪雲飛編著 Spring Boot中的事務管理: https://www.cnblogs.com/sharpest/p/7995203.html 透徹的掌握 Spring 中@transactional 的使用: https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/ 傳智播客《Spring Boot實戰與原理分析》視訊課程 (https://pan.baidu.com/s/1o9M2bGI 密碼:jxg8)