- 對於SQL事務的概念以及ACID性質,可以參見我的另一篇博文 http://kingj.iteye.com/admin/blogs/1675011
- spring的管理的事務可以分爲如下2類:
- 邏輯事務 在spring中定義的事務通常指邏輯事務,提供比物理事務更抽象,方便的事務配置管理,但也基於物理事務
- 物理事務 特定於數據庫的事務
- spring中支持一下2中事務聲明方式
- 編程式事務 當系統需要明確的,細粒度的控制各個事務的邊界,應選擇編程式事務
- 聲明式事務 當系統對於事務的控制粒度較粗時,應該選擇申明式事務
- 無論你選擇上述何種事務方式去實現事務控制,spring都提供基於門面設計模式的事務管理器供選擇,如下是spring事務中支持的事務管理器
-
事務管理器實現(org.springframework.*) 使用時機 jdbc.datasource.DataSourceTransactionManager 使用jdbc的抽象以及ibatis支持 orm.hibernate.HibernateTransactionManager 使用hibernate支持(默認3.0以下版本) orm.hibernate3.HibernateTransactionManager 使用hibernate3支持 transaction.jta.JtaTransactionManager 使用分佈式事務(分佈式數據庫支持) orm.jpa.JpaTransactionManager 使用jpa做爲持久化工具 orm.toplink.TopLinkTransactionManager 使用TopLink持久化工具 orm.jdo.JdoTransactionManager 使用Jdo持久化工具 jms.connection.JmsTransactionManager 使用JMS 1.1+ jms.connection.JmsTransactionManager102 使用JMS 1.0.2 transaction.jta.OC4JJtaTransactionManager 使用oracle的OC4J JEE容器 transaction.jta.WebLogicJtaTransactionManager 在weblogic中使用分佈式數據庫 jca.cci.connection.CciLocalTransactionManager 使用jrping對J2EE Connector Architecture (JCA)和Common Client Interface (CCI)的支持
UML結構圖如下
4、各種事務管理器的定義如下
-
- JdbcTransactionManager定義如下
-
- <bean id=“transactionManager” class=“org.springframework.jdbc.datasource.DataSourceTransactionManager”>
- <property name=“dataSource” ref=“dataSource”/>
- </bean>
- hibernate事務管理器配置如下
-
- <bean id=“transactionManager” class=“org.springframework.orm.hibernate3.HibernateTransactionManager”>
- <property name=“sessionFactory” ref=“sessionFactory”/><span style=“white-space: pre;”> </span>
- </bean>
- hibernate的事務管理器會注入session會話工廠,然後將事務處理委託給當前的transaction對象,事務提交時,調用commit()方法,回滾時調用rollback()方法
- jpa事務管理器配置如下
-
- <bean id=“transactionManager” class=“org.springframework.orm.jpa.JpaTransactionManager”>
- <span style=“white-space: pre;”> </span><property name=“entityManagerFactory” ref=“entityManagerFactory”/>
- </bean>
- 其餘事務管理器可參見spring in action中說明
- 5、申明式事務配置
- spring特有的事務傳播行爲,spring支持7種事務傳播行爲,確定客戶端和被調用端的事務邊界(說得通俗一點就是多個具有事務控制的service的相互調用時所形成的複雜的事務邊界控制)下圖所示爲7鍾事務傳播機制
-
傳播行爲 含義 PROPAGATION_REQUIRED(XML文件中爲REQUIRED) 表示當前方法必須在一個具有事務的上下文中運行,如有客戶端有事務在進行,那麼被調用端將在該事務中運行,否則的話重新開啓一個事務。(如果被調用端發生異常,那麼調用端和被調用端事務都將回滾) PROPAGATION_SUPPORTS(XML文件中爲SUPPORTS) 表示當前方法不必需要具有一個事務上下文,但是如果有一個事務的話,它也可以在這個事務中運行 PROPAGATION_MANDATORY(XML文件中爲MANDATORY) 表示當前方法必須在一個事務中運行,如果沒有事務,將拋出異常 PROPAGATION_NESTED(XML文件中爲NESTED) 表示如果當前方法正有一個事務在運行中,則該方法應該運行在一個嵌套事務中,被嵌套的事務可以獨立於被封裝的事務中進行提交或者回滾。如果封裝事務存在,並且外層事務拋出異常回滾,那麼內層事務必須回滾,反之,內層事務並不影響外層事務。如果封裝事務不存在,則同PROPAGATION_REQUIRED的一樣 PROPAGATION_NEVER(XML文件中爲NEVER) 表示當方法務不應該在一個事務中運行,如果存在一個事務,則拋出異常 PROPAGATION_REQUIRES_NEW(XML文件中爲REQUIRES_NEW) 表示當前方法必須運行在它自己的事務中。一個新的事務將啓動,而且如果有一個現有的事務在運行的話,則這個方法將在運行期被掛起,直到新的事務提交或者回滾才恢復執行。 PROPAGATION_NOT_SUPPORTED(XML文件中爲NOT_SUPPORTED) 表示該方法不應該在一個事務中運行。如果有一個事務正在運行,他將在運行期被掛起,直到這個事務提交或者回滾才恢復執行
- 6、spring中的事務隔離級別
- spring的事務隔離級別其實本質上是對SQL92標準的4種事務隔離級別的一種封裝,具體參加我的博文:http://kingj.iteye.com/admin/blogs/1675011
- spring的事務隔離級別如下表所示
-
隔離級別 含義 ISOLATION_DEFAULT 使用數據庫默認的事務隔離級別 ISOLATION_READ_UNCOMMITTED 允許讀取尚未提交的修改,可能導致髒讀、幻讀和不可重複讀 ISOLATION_READ_COMMITTED 允許從已經提交的事務讀取,可防止髒讀、但幻讀,不可重複讀仍然有可能發生 ISOLATION_REPEATABLE_READ 對相同字段的多次讀取的結果是一致的,除非數據被當前事務自生修改。可防止髒讀和不可重複讀,但幻讀仍有可能發生 ISOLATION_SERIALIZABLE 完全服從ACID隔離原則,確保不發生髒讀、不可重複讀、和幻讀,但執行效率最低。
- 7、spring事務只讀屬性
- spring事務只讀的含義是指,如果後端數據庫發現當前事務爲只讀事務,那麼就會進行一系列的優化措施。它是在後端數據庫進行實施的,因此,只有對於那些有可能啓動一個新事務的傳播行爲(REQUIRED,REQUIRES_NEW,NESTED)的方法來說,纔有意義。(測試表明,當使用JDBC事務管理器並設置當前事務爲只讀時,並不能發生預期的效果,即能執行刪除,更新,插入操作)
- 8、spring的事務超時
- 有的時候爲了系統中關鍵部分的性能問題,它的事務執行時間應該儘可能的短。因此可以給這些事務設置超時時間,以秒爲單位。我們知道事務的開始往往都會發生數據庫的表鎖或者被數據庫優化爲行鎖,如果允許時間過長,那麼這些數據會一直被鎖定,影響系統的併發性。
- 因爲超時時鐘是在事務開始的時候啓動,因此只有對於那些有可能啓動新事物的傳播行爲(REQUIRED,REQUIRES_NEW,NESTED)的方法來說,事務超時纔有意義。
- 9、事務回滾規則
- spring中可以指定當方法執行並拋出異常的時候,哪些異常回滾事務,哪些異常不回滾事務。
- 默認情況下,只在方法拋出運行時異常的時候纔回滾(runtime exception)。而在出現受阻異常(checked exception)時不回滾事務,這個ejb的回滾行爲一致。
- 當然可以採用申明的方式指定哪些受阻異常像運行時異常那樣指定事務回滾。
- 10、spring申明式事務配置
- 將aop,tx命名空間添加到當前的spring配置文件頭中
- 定義一個事務AOP通知
-
- <tx:advice id=“txAdvice” transactionManager=“transactionManager”>
- <tx:attributes>
- <tx:method name=“add*” propagation=“REQUIRED”/>
- </tx:attributes>
- </tx:advice><span style=“white-space: pre;”> </span>
- 定義一個事務切面,即應該在哪些類的哪些方法上面進行事務切入
-
- <aop:config>
- <aop:advisor pointcut=“execution(* *..zx.spring.UserService*.*(..))||execution(* *..spring.ServiceFacade.*(..))||execution(* *..spring.BookService.*(..))” advice-ref=“txAdvice”/>
- </aop:config>
- 11、結合具體的代碼實現講解spring的7種事務傳播機制效果
- 準備環境
- 我們採用JDBC+ORACLE實現具體操作,首先搭建好spring的開發環境,配置好數據源,建立好Test
- 創建數據庫結構,創建一個用戶表和Book表
-
- CREATE TABLE T_USER(
- ID INT,
- NAME VARCHAR2(200)
- );
- CREATE TABLE T_BOOK(
- ID INT ,
- NAME VARCHAR2(200)
- );
- 搭建好的結構如下
-
- <!– 定義數據源 –>
- <bean id=“ams” class=“com.mchange.v2.c3p0.ComboPooledDataSource”
- destroy-method=“close”>
- <property name=“driverClass” value=“${jdbc.ams.driver}” />
- <property name=“jdbcUrl” value=“${jdbc.ams.url}” />
- <property name=“user” value=“${jdbc.ams.username}” />
- <property name=“password” value=“${jdbc.ams.password}” />
- <property name=“initialPoolSize” value=“${initialSize}” />
- <property name=“minPoolSize” value=“${minPoolSize}” />
- <property name=“maxPoolSize” value=“${maxActive}” />
- <property name=“acquireIncrement” value=“${acquireIncrement}” />
- <property name=“maxIdleTime” value=“${maxIdleTime}” />
- </bean>
- <!– 定義jdbc模板類–>
- <bean id=“jdbcTemplate” class=“org.springframework.jdbc.core.JdbcTemplate”>
- <property name=“dataSource” ref=“ams”/>
- </bean>
- 在spring包下面建立3個service
- BookService
-
- package com.zx.spring;
- import org.springframework.jdbc.core.JdbcTemplate;
- public class BookService {
- public static final String ADD_BOOK=“insert into t_book(id,name) values(1,’duck-j2ee’)”;
- private JdbcTemplate jdbcTemplate;
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RollbackException(“跳出執行”);
- }
- public JdbcTemplate getJdbcTemplate() {
- return jdbcTemplate;
- }
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- }
- UserService
-
- package com.zx.spring;
- import org.springframework.jdbc.core.JdbcTemplate;
- public class UserService {
- public static final String ADD_USER=“insert into t_user(id,name) values(1,’duck’)”;
- private BookService bs;
- private JdbcTemplate jdbcTemplate;
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
- public JdbcTemplate getJdbcTemplate() {
- return jdbcTemplate;
- }
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- public BookService getBs() {
- return bs;
- }
- public void setBs(BookService bs) {
- this.bs = bs;
- }
- }
- 創建一個ServiceFacade門面,將UserService和BookService包裝起來
-
- package com.zx.spring;
- public class ServiceFacade {
- private BookService bs;
- private UserService us;
- public BookService getBs() {
- return bs;
- }
- public void setBs(BookService bs) {
- this.bs = bs;
- }
- public UserService getUs() {
- return us;
- }
- public void setUs(UserService us) {
- this.us = us;
- }
- public void addUserBook()throws Exception{
- bs.addBook();
- us.addUser();
- }
- }
- 上面我們配置了3個service接口,並在spring中配置了申明式事務。
-
- <aop:config>
- <aop:advisor pointcut=“execution(* *..zx.spring.UserService*.*(..))||execution(* *..spring.ServiceFacade.*(..))||execution(* *..spring.BookService.*(..))” advice-ref=“txAdvice”/>
- </aop:config>
- 我們在一個pointcut中定義了3個aspectj方式的切入點,即對這3個類的所有方法進行事務切入。
接下來我們開始配置不同的事務傳播機制,來看看效果。
- 到此,準備工作大功告成,接下來我們來對7中傳播機制做一個詳細解釋。
- 一 :我們來對單個方法的事務傳播機制進行一個瞭解
- REQUIRED, REQUIRES_NEW
- junit代碼
-
- @Test
- public void testAddBook()throws Exception{
- BookService bs=(BookService)this.getBean(“bookService”);
- bs.addBook();
- }
- addBook()代碼
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException(“throw runtime exception in outter transaction”);
- }
- 執行junit後,控制檯如下
-
- 1、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 2、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 3、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@a2b392]
- 可以知道,當addBook()方法的事務傳播機制爲REQUIRED, REQUIRES_NEW ,並且拋出運行時異常時,將會回滾事務。
- 當addBook()方法拋出受檢查的異常時,將不會回滾事務。
- addBook()方法如下:
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new Exception(“throw runtime exception in outter transaction”);
- }
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.BookService.addBook]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 3、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 4、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@4310d0]
- 第3行可知,事務提交,不回滾
- MANDATORY
- 代碼同上所示,只是事務傳播機制改爲 MANDATORY
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED” />
- <tx:method name=“addBook” propagation=“MANDATORY”/>
- </tx:attributes>
- </tx:advice>
- 運行junit後,由於單個方法執行沒有指定任何事務傳播機制,因此拋出異常。
-
- org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’
- at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:357)
- NESTED
- NESTED內嵌事務,如果沒有外層事務,則新建一個事務,行爲同REQUIRED一樣
- NEVER
- NEVER不會以事務方式運行,執行junit代碼後,控制檯如下
-
- 1、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 2、[DEBUG,DataSourceUtils,main] Registering transaction synchronization for JDBC Connection
- 3、[DEBUG,DataSourceTransactionManager,main] Should roll back transaction but cannot - no transaction available
- 由於發生運行時異常,事務本應該回滾,但是在第三行可以知道,由於事務傳播機制爲NEVER,因此找不到事務進行回滾,數據庫只添加了一條記錄。
- SUPPORTS
- 單個方法 調用時supports行爲同NEVER一樣,不會創建事務,只是如果有事務,則會加入到當前事務中去,具體的行爲下面有分析。
- NOT_SUPPORTED
- 單個方法被執行時,不會創建事務。如果當前有事務,將封裝事務掛起,知道該方法執行完成再恢復封裝事務,繼續執行。
- REQUIRED, REQUIRES_NEW
二:在瞭解了單個方法的事務傳播機制後,我們來配置多個方法調用之間的傳播機制的行爲
- 1、我們將UserService和BookService中的addUser(),addBook()方法分別配置爲REQUIRED,並在addBook()方法中拋出運行時異常。
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED”/>
- <tx:method name=“addBook” propagation=“REQUIRED”/>
- </tx:attributes>
- </tx:advice>
-
- 1、public void addUser()throws Exception {
- 2、 this.bs.addBook();
- 3、 this.jdbcTemplate.execute(ADD_USER);
- }
-
- 4、public void addBook() throws Exception{
- 5、 this.jdbcTemplate.execute(ADD_BOOK);
- 6、 throw new RuntimeException(“跳出執行”);
- }
- 測試用例如下
-
- @Test
- public void testAddUser()throws Exception{
- UserService us=(UserService)this.getBean(“userService”);
- us.addUser();
- }
- 執行前數據庫如下
- 執行後,console控制檯輸出如下:
-
- [DEBUG,DataSourceTransactionManager,main]
- Creating new transaction with name [com.zx.spring.UserService.addUser]:
- PROPAGATION_REQUIRED,ISOLATION_DEFAULT
上面輸出可以知道,spring自動給addUser()方法切入了事務,事務隔離級別爲數據庫默認級別。 - 我們再觀察控制檯輸出
-
- 1、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] for JDBC transaction
- 2、[DEBUG,DataSourceTransactionManager,main] Switching JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] to manual commit
- 3、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 4、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 5、[DEBUG,DataSourceTransactionManager,main] Participating transaction failed - marking existing transaction as rollback-only
- 6、[DEBUG,DataSourceTransactionManager,main] Setting JDBC transaction [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] rollback-only
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5]
- 第一行jdbc事務管理器從c3p0連接池中獲取一個鏈接
- 第二行設置jdbc事務提交方式爲手動提交
- 代碼執行到方法addUser()中第一行時,由於addUser()方法的事務級別爲REQUIRED的因此,事務管理器開始了一個事務。執行到第二行addBook()時,由於addBook()方法的事務傳播行爲爲REQUIRED的,我們知道REQUIRED方式是如果有一個事務,則加入事務中,如果沒有,則新建一個事務。由控制檯輸出的第3行可以知道,addBook方法加入到了addUser方法的事務當中去,接着第4行執行了插入t_book語句,由於addBook()方法在第6行時,拋出了運行時異常,因此當前事務失敗,可從控制檯輸出第5行得知。
- 由於addUser()和addBook()方法共享了一個事務,在addBook()方法中又拋出了運行時異常,因此事務必須回滾,這由數據庫查詢可知。
- 如果我們將addBook()方法中拋出的運行時異常改爲checked異常的話,會是什麼結果呢?
-
- 7、public void addBook() throws Exception{
- 8、 this.jdbcTemplate.execute(ADD_BOOK);
- 9、 throw new Exception(“跳出執行”);
- }
-
- 9、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] for JDBC transaction
- 10、[DEBUG,DataSourceTransactionManager,main] Switching JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] to manual commit
- 11、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 12、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 13、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 14、[DEBUG,DataSourceTransactionManager,main] Releasing JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@457d21] after transaction
- 由控制檯輸出第13行可以知道,除了addBook()方法中拋出的檢查異常被忽略之外,其它的同上面的一致。再看數據庫可以知道,addBook()方法和被執行了,addUser()方法被拋出的檢查異常終止調用。
- 如果我們給addUser()方法指定rollback-for屬性,那麼addBook()方法的事務回回滾嗎?
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new Exception(“跳出執行”);
- }
-
- <tx:advice id=”txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=”addUser” propagation=“REQUIRED” <span style=“color: #ff0000;”><span style=“color: #888888;”>rollback-for=“Exception”</span></span>/>
- <tx:method name=”addBook” propagation=“REQUIRED”/>
- </tx:attributes>
- </tx:advice>
- 控制檯輸出如下
-
- 15、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 16、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 17、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 18、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@121d383]
- 2、上面我們討論了兩個方法都指定爲事務傳播機制爲REQUIRED,那麼我們來改變以下addBook()方的事務傳播機制改爲NEVER ,來看看它們的效果
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED” />
- <tx:method name=“addBook” propagation=“NEVER”/>
- </tx:attributes>
- </tx:advice>
-
- org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’
- at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:399)
- at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:347)
- at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
- 3、我們接着將addBook()方法的事務傳播機制改爲MANDATORY
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED” />
- <tx:method name=“addBook” propagation=“MANDATORY”/>
- </tx:attributes>
- </tx:advice>
-
- [DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- [DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- [DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,‘duck’)]
- [DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
-
- 4、我們將addBook()方法的事務傳播機制改爲NESTED-內嵌事務,那麼傳播機制之間會怎麼互相影響呢?
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED” />
- <tx:method name=“addBook” propagation=“NESTED”/>
- </tx:attributes>
- </tx:advice>
- addBook()方法如下,依然拋出(checked-exception)檢查異常。
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException(“跳出執行”);
- }
- addUser()方法如下,在方法體中捕獲addBook()拋出的異常。如果不捕獲異常,addUser()方法將會被終止。
- public void addUser()throws Exception {
- try {
- this.bs.addBook();
- }catch(Exception e) {
- e.printStackTrace();
- }
- this.jdbcTemplate.execute(ADD_USER);
- }
執行junit後,控制檯輸出如下 -
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Creating nested transaction with name [com.zx.spring.BookService.addBook]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 4、[DEBUG,DataSourceTransactionManager,main] Rolling back transaction to savepoint
- 5、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,‘duck’)]
- 6、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 7、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@10e164e]
- 由上面的輸出可以,第一行爲執行addUser()方法,事務管理器開始了一個事務,
- 第二行執行到addBook()方法,由於addBook()方法的事務傳播機制爲NESTED內嵌事務,因此,開始一個新的事務。
- 第三行可以知道插入語句,由於addBook()方法內部拋出RuntimeException,因此內部嵌套事務回滾到外層事務創建的保存點。
- 注意這個地方,我們拋出的是運行時異常,如果我們拋出受檢查的異常,那麼spring會默認的忽略此異常。下面我會詳細闡述。
- 如果內層事務拋出檢查異常,那麼外層事務將忽略此異常,但是會產生一個問題。那就是:外層事務使用jdbc的保存點API來實現嵌套事務,
- 但是數據庫不一定支持。我做測試的是oracle數據庫,jdbc事務管理器在內層事務拋出檢查異常後,將會在內層事務結束後,釋放外層事務
- 創建的保存點,這是時候數據庫不一定支持。因此可能會拋出如下異常:
-
- java.sql.SQLException: 不支持的特性
- at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)
- at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:146)
- at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:208)
- 第五行可以知道,外層事務開始執行,第六行可知外層事務提交。
- 總結可知:對於NESTED內層事務而言,內層事務獨立於外層事務,可以獨立遞交或者回滾。
- 如果我們在addUser方法內部拋出一個運行時異常,那麼會怎麼樣呢?
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- throw new RuntimeException(“throw runtime exception in outter transaction”);
- }
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- }
- 執行junit後,控制檯輸入如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Creating nested transaction with name [com.zx.spring.BookService.addBook]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 4、[DEBUG,DataSourceTransactionManager,main] Releasing transaction savepoint
- 5、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,‘duck’)]
- 6、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 7、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1622a94]
- 第一行在addUser()方法執行後,事務管理器創建一個新的事務。
- 第二上和上面一樣,由於addBook()方法是NETSTED的內嵌傳播機制,因此新建一個事務。
- 執行插入,釋放保存點。
- 執行插入t_user插入,但是此時拋出了一個運行時異常,外層事務回滾,那麼內層事務是否回滾呢?我們看以下數據庫記錄。
- t_user表數據爲空,事務回滾了
t_book表數據也爲空,證明內層事務回滾了
- 由上述結果可知,如果對於一個內嵌事務來說,外層事務的回滾必將導致內層事務回滾。
-
- 5、我們再將addBook()方法的事務傳播機制該爲REQUIRES_NEW,來看看會有什麼有趣的事情發生?
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED” />
- <tx:method name=“addBook” propagation=“REQUIRES_NEW”/>
- </tx:attributes>
- </tx:advice>
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- //throw new RuntimeException(“throw runtime exception in outter transaction”);
- }
- 執行junit後,控制檯輸出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 4、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 5、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@5b96c2]
- 由上可知,第一行執行addUser()方法創建一個事務,
- 第二行阻塞addUser()方法,並創建一個新的事務,執行插入t_book表,提交內層事務和外層事務。
- 或許有的讀者會問,在下面addUser()方法中由於第一行和第二行是順序執行,因此不能說明說明問題,那麼我們將addUser()方法中的1、2行代碼調換,在看效果:
-
- public void addUser()throws Exception {
- 1、 this.bs.addBook();
- 2、 this.jdbcTemplate.execute(ADD_USER);
- }
- 兌換後的代碼
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- this.bs.addBook();
- }
- 在來看控制檯輸出
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,‘duck’)]
- 3、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 4、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 5、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 6、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1b7ae22]
- 7、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 8、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 9、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1fa681c]
- 由第一、二行可知道正在執行插入t_user表操作,而到第3行中我們可以知道,插入t_user表的事務被掛起,並且新建了一個事務來插入t_book表
- t_book表插入事務提交後,到第7行可知,前一個事務t_user插入操作被恢復,並提交前一個操作。
-
- 如果我們在addBook()方法中拋出運行時異常,來看看會有什麼有趣的事情發生?
- addBook()方法代碼如下
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException(“throw runtime exception in outter transaction”);
- }
- addUser()方法代碼如下
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- this.bs.addBook();
- }
- 執行junit後,控制檯輸出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,‘duck’)]
- 3、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 4、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 5、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 6、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@10d0b72]
- 7、[DEBUG,DataSourceTransactionManager,main] Releasing JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@10d0b72] after transaction
- 8、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction8、
- 9、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 10、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@44b361]
- 11、[DEBUG,DataSourceTransactionManager,main] Releasing JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@44b361] after transaction
- 第一行可知,執行addUser()方法時,從鏈接池獲取一個新鏈接,創建一個封裝事務,執行t_user表插入。
- 第三行可知,t_user插入事務被掛起,一直到第7行,插入t_book表事務被回滾
- 第8行可知,t_user事務恢復,但是此時該封裝事務被回滾。我們再看數據庫.
- t_user表數據和t_book表數據均爲空
- 由此我們可以知道,對於REQUIRES_NEW事務傳播機制,如果被調用端拋出運行時異常,則被調用端事務回滾,那麼調用端的事務到底是回滾還是提交呢?
- 如果調用段代碼捕獲了被調用端拋出的運行時異常,那麼調用端事務提交,不回滾
- 我們將addUser()調用端代碼該成如下(捕獲addBook()拋出的運行時異常)
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- try {
- this.bs.addBook();
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- 執行junit後,控制檯信息如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1bcec19] for JDBC transaction
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,‘duck’)]
- 4、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 5、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@ba507b] for JDBC transaction
- 6、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@ba507b]
- 9、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 10、java.lang.RuntimeException: throw runtime exception in outter transaction
- at com.zx.spring.BookService.addBook(BookService.java:11)
- 11、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 12、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1bcec19]
- 由上面的輸出可以知道,1-3行從連接池獲取一個鏈接,開始執行插入事務
- 執行addBook()方法時,因其事務傳播屬性爲REQUIRES_NEW,則將上一個事務阻塞
- 第6-8行可知,addBook()方法拋出運行時異常,新事務被回滾
- 第9行恢復執行上一個插入t_user表事務,並捕獲到addBook()拋出的異常,自此addUser()方法未拋出任何運行時異常,提交事務。
- 如果調用端未捕獲被調用端拋出的運行時異常,那麼調用端事務回滾,不提交
- 我們將addUser()方法調用端改成如下(不捕獲addBook()拋出的運行時異常,直接拋出)
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- this.bs.addBook();
- }
- 執行junit後,控制檯輸出如下:
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,‘duck’)]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 4、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 5、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@63a721]
- 6、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1706da8]
- 由上述第1-5行可知,插入t_user表事務被掛起,同時插入t_book事務被回滾,因爲拋出了運行時異常。
- 6-8行插入t_user事務被回滾,因爲addUser()方法的事務傳播界別爲REQUIRED,因此在拋出了運行時異常的情況下,會回滾事務。
- 那麼,爲什麼會造成上面兩種截然不同的結果呢?因爲addUser()方法被聲明爲REQUIRED傳播機制,只要它拋出運行時異常,均會回滾事務。
- 如果調用段代碼捕獲了被調用端拋出的運行時異常,那麼調用端事務提交,不回滾
- 6、 我們再將addBook()方法的事務傳播機制該爲SUPPORTS,來看看會有什麼有趣的事情發生?
- 將addBook()方法的事務機制改爲SUPPORTS
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED” />
- <tx:method name=“addBook” propagation=“SUPPORTS”/>
- </tx:attributes>
- </tx:advice>
- addUser()方法
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
- addBook()方法
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException(“throw runtime exception in outter transaction”);
- }
- 執行junit後,控制檯輸出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 4、[DEBUG,DataSourceTransactionManager,main] Participating transaction failed - marking existing transaction as rollback-only
- 5、[DEBUG,DataSourceTransactionManager,main] Setting JDBC transaction [com.mchange.v2.c3p0.impl.NewProxyConnection@19e09a4] rollback-only
- 6、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 7、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@19e09a4]
- 由第二行可知,addBook()方法的加入到了當前addUser()方法的事務中,第4行可知,addBook()方法拋出運行時異常,此時addUser()方法的事務被標記爲rollback,整個事務都
- 將回滾。如果addUser()方法沒有任何事務,那麼addBook()方法也不會在事務環境中執行。不管是否拋出異常,sql都將執行。
- 如果addBook()方法拋出受檢查的異常,那麼此異常將忽略,整個addUser()方法的事務將提交。addBook()方法也不會嘗試去回滾事務
- 7、 我們再將addBook()方法的事務傳播機制該爲NOT_SUPPORTED,會怎麼樣呢?
- 將addBook()方法的事務機制該爲NOT_SUPPORTED
-
- <tx:advice id=“txAdvice” transaction-manager=“txManager”>
- <tx:attributes>
- <tx:method name=“addUser” propagation=“REQUIRED” />
- <tx:method name=“addBook” propagation=“NOT_SUPPORTED”/>
- </tx:attributes>
- </tx:advice>
- addBook()方法
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException(“throw runtime exception in outter transaction”);
- }
- addUser()方法
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
- 執行junit後,控制檯輸出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,‘duck-j2ee’)]
- 4、[DEBUG,DataSourceUtils,main] Fetching JDBC Connection from DataSource
- 5、[DEBUG,DataSourceTransactionManager,main] Should roll back transaction but cannot - no transaction available
- 6、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@137d4a4]
- 由上可知,第二行是將addUser()方法的事務掛起,開始執行addBook()代碼,
- 第5行可知,addBook()方法拋出運行時異常,需要回滾事務,但是又沒有事務來回滾,因此t_book表數據被插入
- 由於addBook()拋出異常,在addUser()方法中未捕獲該異常,因此對addUser()方法的事務傳播機制REQUIRED來說,拋出了運行時異常,addUser()方法回滾
- 如果將addUser()方法該爲如下:
-
- public void addUser()throws Exception {
- try {
- this.bs.addBook();
- }catch(Exception e) {
- e.printStackTrace();
- }
- this.jdbcTemplate.execute(ADD_USER);
- }
- 那麼addUser()方法將會提交,addBook()方法將插入一條數據到t_book表中,
- 如果addBook()方法拋出了受檢查異常,addBook()方法將忽略此異常,不嘗試任何事務回滾,同樣即使在addUser()方法中不捕獲addBook()方法拋出的受檢查異常,addUser()方法也會提交事務,而忽略此異常。
今天對spring事務7中傳播機制之間的作用進行了一個回顧,這裏只涉及了jdbc事務管理器的特性,可能會有各種疏忽,希望各位讀者拍磚。
</div>
</div>