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 及具體實現類關系下圖所示:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM