純轉載文章,記錄一下自己學習學習。
原文地址:springboot+mybatis事務管理
spring,mybatis事務管理配置與@Transactional註解使用
概述
事務管理對於企業應用來說是至關重要的,即使出現異常情況,它也可以保證數據的一致性。
Spring Framework對事務管理提供了一致的抽象,其特點如下:
爲不同的事務API提供一致的編程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)支持聲明式事管理,特別是基於註解的聲明式事務管理,簡單易用提供比其他事務API如JTA更單的編程式事務管理API與spring數據訪問抽象的完美集成
事務管理方式
spring支持編程式事務管理和聲明式事務管理兩種方式。
編程式事務管理使用TransactionTemplate
或者直接使用底層的PlatformTransactionManager
。對於編程式事務管理,spring推薦使用TransactionTemplate
。
聲明式事務管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基於@Transactional
註解的方式),便可以將事務規則應用到業務邏輯中。
顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,後者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立爲方法等等。
聲明式事務管理也有兩種常用的方式,一種是基於tx和aop名字空間的xml配置文件,另一種就是基於@Transactional
註解。顯然基於註解的方式更簡單易用,更清爽。
spring事務特性
spring所有的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager
接口
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
除了指定不同的事務管理器之後,還能對事務進行隔離級別和傳播行爲的控制,下面分別詳細解釋:
隔離級別
隔離級別是指若干個併發的事務之間的隔離程度,與我們開發時候主要相關的場景包括:髒讀取、重複讀、幻讀。
對於髒讀、不可重複讀和幻讀詳見:髒讀取、重複讀、幻讀
我們可以看org.springframework.transaction.annotation.Isolation
枚舉類中定義了五個表示隔離級別的值:
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
- DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是:READ_COMMITTED。
- READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止髒讀和不可重複讀,因此很少使用該隔離級別。
- READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止髒讀,這也是大多數情況下的推薦值。
- REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的數據滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止髒讀和不可重複讀。
- SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
指定方法:通過使用isolation屬性設置,例如:
@Transactional(isolation = Isolation.DEFAULT)
傳播行爲
所謂事務的傳播行爲是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行爲。
我們可以看org.springframework.transaction.annotation.Propagation
枚舉類中定義了6個表示傳播行爲的枚舉值:
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
- REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。
- SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
- MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
- REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
- NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
- NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
- NESTED:如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於REQUIRED。
指定方法:通過使用propagation屬性設置,例如:
@Transactional(propagation = Propagation.REQUIRED)
事務超時
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
默認設置爲底層事務系統的超時值,如果底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。
事務只讀屬性
只讀事務用於客戶代碼只讀但不修改數據的情形,只讀事務用於特定情景下的優化,比如使用Hibernate的時候。
默認爲讀寫事務。
spring事務回滾規則
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然後依據規則決定是否回滾拋出異常的事務。
默認配置下,spring只有在拋出的異常爲運行時unchecked異常時纔回滾該事務,也就是拋出的異常爲RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。
可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。
還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()後你所能執行的唯一操作就是回滾。
@Transactional註解
Xml方式配置:
<tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
用法
@Transactional 可以作用於接口、接口方法、類以及類方法上。當作用於類上時,該類的所有 public 方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。
雖然 @Transactional 註解可以作用於接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該註解,因爲這隻有在使用基於接口的代理時它纔會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常。
默認情況下,只有來自外部的方法調用纔會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法並不會引起事務行爲,即使被調用方法使@Transactional註解進行修飾。
在使用@Transactional註解前,請在啓動類上加上註解@EnableTransactionManagement來開啓事務。
@EnableTransactionManagement //開啓事務
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
使用示例:
@Service
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,rollbackFor = Exception.class) //直接配置在接口上
public interface AnnounceService {
}
運行時,可能會有個報錯,
The bean 'xxx' could not be injected as a 'xx.xxxx' because it is a JDK dynamic proxy that implements:
解決方法:啓動類添加@EnableTransactionManagement(proxyTargetClass = true)
因爲加了@Transaction的類會自動開啓動態代理,java的代理機制主要有JDK動態代理和CGLIB,報上面的錯誤原因是將註解標記在接口上時,只有使用動態代理的時候纔會生效,需要標記proxy-target-class="true"或者mode=“aspectj”。詳情參見:spring-boot 事務異常: because it is a JDK dynamic proxy that implement
@EnableTransactionManagement(proxyTargetClass = true) //開啓事務
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
注:
java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。
而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。
1、如果目標對象實現了接口,默認情況下會採用JDK的動態代理實現AOP
2、如果目標對象實現了接口,可以強制使用CGLIB實現AOP
3、如果目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換
關於事務和AOP的關係,請見:
Spring的事務管理和Aop
參考鏈接:
springboot事務管理詳解
spring,mybatis事務管理配置與@Transactional註解使用
SpringBoot聲明式事務的簡單運用