Spring數據庫事務管理器的設計java
在Spring中數據庫事務是經過PlatformTransactionManager進行管理的。附TransactionTemplate重要源碼。spring
@Override @Nullable public <T> T execute(TransactionCallback<T> action) throws TransactionException { Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); //使用自定義的事務管理器 if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); }else {//系統默認管理器 //獲取事務狀態 TransactionStatus status = this.transactionManager.getTransaction(this); T result; try { result = action.doInTransaction(status);//回調接口方法 }catch (RuntimeException | Error ex) { // 回滾異常方法 rollbackOnException(status, ex); throw ex; }catch (Throwable ex) { // 回滾異常方法 rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } this.transactionManager.commit(status);//提交事務 return result;//返回結果 } }
能夠清楚的看到:數據庫
配置事務管理器express
目前咱們討論JDBC和MyBatis,使用最多的事務管理器是DataSourceTransactionManager,其它持久層框架後續介紹。編程
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="com.wise.tiger"/> <context:property-placeholder location="classpath:dbcp-config.properties"/> <!-- 配置數據源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" > <property name="driverClassName" value="${driverClassName}" /> <property name="url" value="${url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${password}" /> <property name="defaultAutoCommit" value="${defaultAutoCommit}"/> <property name="connectionProperties" value="${connectionProperties}"/> </bean> <!--配置數據源事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
在spring中可使用聲明式事務和編程式事務,現在編程式事務幾乎不用了,由於它會產生冗餘,代碼可讀性較差。聲明式事務又能夠分爲xml配置和註解事務,但xml方式已經不經常使用了,因此這裏只簡單交代下它的用法,目前主流方法是註解@Transactional。安全
聲明式事務網絡
聲明式事務是一種約定性的事務,在大部分的狀況下,使用數據庫事務時,大部分場景是在代碼中發生了異常時,須要回滾事務,而不發生異常時則是提交事務,從而保證數據庫數據的一致性。從這點出發,Spring給了一個約定(相似於AOP開發給的約定),你的業務方法不發生異常,事務管理器就提交事務,發生異常則讓事務管理器回滾事務。併發
首先聲明式事務容許自定義事務接口——TransactionDefinition,它能夠由xml或者註解@Transactional進行配置,到了這裏咱們先談談@Transactional的配置項app
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
Transactional的配置項框架
配置項 | 含義 | 備註 |
value | 定義事務管理器 | spring容器中的一個Bean id, 這個Bean須要實現接口PlatformTransactionManager |
transactionManager | 同上 | 同上 |
isolation | 隔離級別 | 數據庫在併發事務時的概念,默認取值爲數據庫的隔離級別 |
propagation | 傳播行爲 | 方法之間調用問題,默認取值爲Propagation.REQUIRED |
timeout | 超時時間 | 單位爲秒,當超時時,會引起異常,默認會致使事務回滾 |
readOnly | 是否開啓只讀事務 | 默認值:false |
rollbackFor | 回滾事務的異常類定義 | 只有當方法產生所定義的異常時,纔會回滾事務 |
rollbackForClassName | 回滾事務的異常類名定義 | 同rollbackFor,只是使用類名稱定義 |
noRollbackFor | 當產生哪些異常不回滾事務 | 當產生所定義異常時,Spring將繼續提交事務 |
noRollbackForClassName | 同noRollbackFor | 同noRollbackFor,只是使用類名稱定義 |
只須要在xml配置文件中加入以下配置就可使用@EnableTransactionManagement@Transactional配置事務了
<tx:annotation-driven transaction-manager="txManager"/>
** 使用xml進行配置事務管理器**
<!-- 配置事務通知 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 根據方法名指定事務的屬性 --> <tx:method name="bookService" propagation="REQUIRED"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 配置事務切入點,以及把事務切入點和事務屬性關聯起來 --> <aop:config> <aop:pointcut expression="execution(* com.wise.tiger.service.*.*(..))" id="txPointcut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>
選擇隔離級別
在互聯網應用中,不但要考慮數據庫數據的一致性,並且還要考慮系統的性能,通常而言,從髒讀到序列化,系統性能直線降低。所以設置高的級別,好比序列化,會嚴重壓制併發,從而引起大量的線程掛起,直到得到鎖才能進一步操做,而恢復時又須要大量的等待時間。所以在通常的在購物類應用中,經過隔離級別來控制事務一致性的方式又被排除了,而對於髒讀又風險過大,在大部分場景下,企業會選擇讀/寫提交的方式設置事務,這樣既有助於提升併發,又壓制了髒讀,可是對於數據一致性問題並無解決,後面討論解決。對於通常的應用均可以使用@Transactional方法進行配置。
隔離級別須要根據併發的大小和性能來作出決定,對於併發不大又要保證數據安全性的可使用序列化的隔離級別,這樣就能保證數據庫在多事務環境中的一致性。
@Transactional(isolation = Isolation.SERIALIZABLE) public void save(Book book) { bookDao.insertBook(book); }
只是這樣的代碼會使得數據庫的併發能力低下,在搶購商品的場景下出現卡頓的狀況,因此在高併發的場景下這樣的代碼並不適用。
註解@Transactional的默認隔離級別是Isolation.DEFAULT,其含義是默認的,隨數據庫的默認值而變化。由於不一樣的數據庫支持的隔離級別是不同的,MySQL支持所有四種隔離級別,默認爲可重複讀的隔離級別。而Oracle只支持讀/寫提交和序列化,默認讀/寫提交.
傳播行爲
傳播行爲是指方法之間的調用事務策略問題。在大部分狀況下,咱們都但願事務可以同時成功或者同時失敗。但也會有例外,假如如今須要實現信用卡的還款功能,有一個總的代碼調用邏輯————RepaymentBatchService的batch方法,那麼它要實現的是記錄還款成功的總卡數和對應完成的信息,而每一張卡的還款則是經過RepaymentService的repay方法完成的。
首先來分析業務。若是隻有一條事務,那麼當調用RepaymentService的repay方法對某一張信用卡進行還款時,不幸的事發生了,它發生了異常。若是將這條事務回滾,就會形成全部的數據操做都會回滾,那些已經正常還款的用戶也會還款失敗,這將是一個糟糕的結果。當batch方法調用repay方法時,它會爲repay方法建立一個新的事務。當這個方法產生異常時,只會回滾它自身的事務,而不會影響主事務和其它事務,這樣就避免了上面的問題
Spring中傳播行爲的類型是經過一個枚舉去定義的org.springframework.transaction. annotation.Propagation
傳播行爲 | 含義 | 備註 |
REQUIRED | 當方法調用時,若是不存在當前事務,那麼就建立事務;若是以前的方法已經存在事務了,那麼就沿用以前的事務 |
spring默認 |
SUPPORTS | 支持當前事務,若是當前沒有事務,就以非事務方式執行。 | |
MANDATORY | 方法必須在事務內運行 | 使用當前的事務, 若是當前沒有事務,就拋出異常。 |
REQUIRES_NEW | 新建事務,若是當前存在事務,把當前事務掛起 | 事務管理器會打開新的事務運行該方法 |
NOT_SUPPORTED | 以非事務方式執行操做,若是當前存在事務,就把當前事務掛起,直到該方法結束才恢復當前事務 | 適用於那些不須要事務的SQL |
NEVER | 以非事務方式執行,若是當前存在事務,則拋出異常。 | |
NESTED | 嵌套事務,也就是調用方法若是拋出異常只回滾本身內部執行的SQL,而不回滾主方法的SQL | 它的實現存在兩種狀況:1.若是當前數據庫支持保存點(savepoint),那麼它就會在當前事務上使用保存點技術;若是發生異常,則將方法內執行的SQL回滾到保存點上,而不是所有回滾;不然就等同於REQUIRES_NEW建立新的事務運行方法代碼 |
通常而言,企業級應用中主要關注的是REQUEIRES_NEW和NESTED
@Service public class BookServiceImpl implements BookService { @Autowired private BookMapper bookMapper; // 傳播行爲定義爲REQUIRED @Override @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED,timeout = 1) public int insertBookList(List<Book> bookList) { int count=0; bookList.forEach(book-> count += this.insertBook(book));//調用本身的方法,產生自調用問題 return count; } // 傳播行爲定義爲REQUIRES_NEW,每次調用產生新事務 @Override @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW,timeout = 1) public int insertBook(Book book) { return bookMapper.insertBook(book); } }
經過測試,在insertBook上標註的@Transactional失效了,這是一個很容易掉進去的陷阱。
出現這個問題的根本緣由在於AOP的實現原理。@Transactional實現原理是AOP,而AOP的實現原理是動態代理,類中的方法調用本身的另外一個方法,並不存在代理對象的調用,這樣就不會產生AOP去爲咱們設置@Transactional配置的參數,這樣就出現了自調用註解失效的問題。爲了解決這個問題,一方面咱們可使用兩個服務類,另一方面能夠直接從容器中獲取service的代理對象。
典型錯誤用法剖析