spring事務管理
1、spring事務管理基本介紹
Spring 支持編程式事務管理以及聲明式事務管理兩種方式。
編程式事務管理是侵入性事務管理,編程式事務每次實現都要單獨實現,但業務量大功能復雜時,使用編程式事務無疑是痛苦的,所以并不推薦使用。
聲明式事務屬于無侵入式,不會影響業務邏輯的實現,只需要在配置文件中做相關的事務規則聲明或者通過注解的方式,便可以將事務規則應用到業務邏輯中。聲明式事務管理建立在AOP之上,其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,執行完目標方法之后根據執行的情況提交或者回滾。顯然聲明式事務管理要優于編程式事務管理,這正是Spring倡導的非侵入式的編程方式。
Spring事務的本質其實就是數據庫對事務的支持,使用JDBC的事務管理機制就是利用 java.sql.Connection 對象完成對事務的提交。在沒有Spring幫我們管理事務之前,我們的做法:
Connection conn = DriverManager.getConnection(); try { conn.setAutoCommit(false); //將自動提交設置為false //執行CRUD操作 ... conn.commit(); //當兩個操作成功后手動提交 } catch (Exception e) { conn.rollback(); //一旦其中一個操作出錯都將回滾,所有操作都不成功 e.printStackTrace(); } finally { conn.colse(); }
事務是一系列的動作,一旦其中有一個動作出現錯誤,必須全部回滾,系統將事務中對數據庫的所有已完成的操作全部撤消,滾回到事務開始的狀態,避免出現由于數據不一致而導致的接下來一系列的錯誤。事務的出現是為了確保數據的完整性和一致性,在目前企業級應用開發中,事務管理是必不可少的。
在企業級應用中,多用戶訪問數據庫是常見的場景,這就是所謂的事務的并發。事務并發所可能存在的問題:
- 臟讀:一個事務讀到另一個事務未提交的更新數據。
- 不可重復讀:一個事務兩次讀同一行數據,可是這兩次讀到的數據不一樣。
- 幻讀:一個事務執行兩次查詢,但第二次查詢比第一次查詢多出了一些數據行。
- 丟失更新:撤消一個事務時,把其它事務已提交的更新的數據覆蓋了。
有了Spring,我們再也無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作,使得我們把更多的精力放在處理業務上。事實上Spring并不直接管理事務,而是提供了多種事務管理器。他們將事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。
具體的事務管理機制對 Spring 來說是透明的,它并不關心那些,那些是對應各個平臺需要關心的,所以 Spring 事務管理的一個優點就是為不同的事務API提供一致的編程模型,如JTA、JDBC、Hibernate、JPA。
Spring并不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。 Spring事務管理器的接口是 org.springframework.transaction.PlatformTransactionManager,通過這個接口,Spring為各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現由各個平臺自己實現。
事務管理器接口 PlatformTransactionManager 通過 getTransaction(TransactionDefinition definition) 方法來得到事務,這個方法里面的參數是 TransactionDefinition 類,這個類就定義了一些基本的事務屬性。事務屬性包含了5個方面,如圖所示:

2、聲明式事務管理之基于注解方式
如果應用程序中直接使用JDBC來進行持久化,DataSourceTransactionManager會為你處理事務邊界。為了使用DataSourceTransactionManager,你需要使用如下的XML將其裝配到應用程序的上下文定義中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
然后需要開啟事務注解,開啟事務注解需要引入命名空間 tx。完整的配置文件如下:
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!--開啟組件掃描--> <context:component-scan base-package="test, service, dao"></context:component-scan> <!--引入外部配置文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置數據庫連接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <!--通過${}使用外部配置文件的值--> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.username}"></property> <property name="password" value="${prop.password}"></property> </bean> <!-- 配置JdbcTmplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 創建事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 開啟事務注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
然后在需要事務管理的地方加上 @Transactional 注解即可,在類上或者在方法上添加該注解都可以。如果注解是添加到類上,則表示給該類里的所有方法都添加事務;如果是添加到方法上,則表示給該方法添加事務。
假設類有一個方法,A給B轉賬,需要給 A 的賬戶減少100元,同時需要給 B 增加100元,如果沒有使用事務,而執行過程中發生了異常,則可能導致數據發生不一致的問題:
@Transactional @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public void updateUser(User user) { userDao.updateUser(user); } @Override public void transferAccount() { //減少100元 userDao.reduceMoney(); //模擬異常 int i = 100 / 0; //增加100元 userDao.addMoney(); } }
如果沒有添加 @Transactional 注解,則 A 減少了100元,但后面發生了異常,導致 B 并沒有增加100元,由此導致了數據不一致的問題。如果添加了注解,則發生異常會自動回滾,A賬戶的金額不會減少,B賬戶也不會增加。
2.1、定義事務的傳播行為
多事務方法(事務方法指的是將改變數據庫表數據的操作)之間互相調用時,對事務之間的管理就稱之為傳播行為(propagation behavior)。當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,并在自己的事務中運行。
Spring 中定義了七種傳播行為:
| 傳播行為 | 值 | 含義 |
| PROPAGATION_REQUIRED(默認值) | 0 | 表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啟動一個新的事務,并在自己的事務內運行 |
| PROPAGATION_SUPPORTS | 1 | 表示當前方法不需要事務上下文,但是如果存在當前事務的話,那么該方法會在這個事務中運行 |
| PROPAGATION_MANDATORY | 2 | 表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常 |
| PROPAGATION_REQUIRED_NEW | 3 | 表示當前方法必須運行在它自己的事務中。一個新的事務將被啟動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
| PROPAGATION_NOT_SUPPORTED | 4 | 表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
| PROPAGATION_NEVER | 5 | 表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則會拋出異常 |
| PROPAGATION_NESTED | 6 |
表示如果當前已經存在一個事務,那么該方法將會在嵌套事務中運行。嵌套的事務可以獨立于當前事務進行單獨地提交或回滾。如果當前事務不存在,那么其行為與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行為的支持是有所差異的。可以參考資源管理器的文檔來確認它們是否支持嵌套事務 |
定義傳播行為只需在 @Transactional 注解后面添加 propagation 參數即可:
@Transactional(propagation = Propagation.REQUIRED) @Service public class UserServiceImpl implements UserService { }
假設 A 方法調用了 B 方法,給 B 定義了 REQUIRED 傳播行為。如果 A 方法已經添加了事務,則 B 方法也在 A 的事務內運行;如果 A 方法沒有事務,則 B 方法會啟動一個新事務,并在該事務內運行。
2.2、定義事務的隔離級別
MySQL數據庫為我們提供了四種隔離級別:
- Read uncommitted (讀未提交):最低級別,任何情況都無法保證。
- Read committed (讀已提交):只可避免臟讀的發生。
- Repeatable read (可重復讀,默認值):可避免臟讀、不可重復讀的發生。
- Serializable (串行化):臟讀、不可重復讀、幻讀均可避免
事務的隔離級別可參考:http://www.rzrgm.cn/wenxuehai/p/13485440.html
spring 設置事務的隔離級別只需要在 @Transactional 注解后面添加 isolation 參數即可,如下設置為 Read commited 級別:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED) @Service public class UserServiceImpl implements UserService { }
2.3、@Transactional 注解的其它參數
2.3.1、超時時間(timeout)
為了使應用程序很好地運行,事務不能運行太長的時間。因為事務可能涉及對后端數據庫的鎖定,所以長時間的事務會不必要的占用數據庫資源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那么就會自動回滾,而不是一直等待其結束。
定義超時時間會規定事務必須在一定時間內進行提交,如果超時會自動進行回滾。
spring 設置事務的超時時間只需要在 @Transactional 注解后面添加 timeout 參數即可。該值默認是 -1,即永不超時。我們可以手動設置超時時間,單位為秒:
@Transactional(timeout = 10) @Service public class UserServiceImpl implements UserService { }
2.3.2、是否只讀(readOnly)
事務的其中一個特性是它是否為只讀事務,即只查詢。如果事務只對后端的數據庫進行該操作,數據庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設置為只讀,你就可以給數據庫一個機會,讓它應用它認為合適的優化措施。
spring 設置事務是否只讀只需要在 @Transactional 注解后面添加 readOnly 參數即可,默認為 false,非只讀,即可讀可寫。我們可以手動設置為 true,即只讀:
@Transactional(readOnly = true) @Service public class UserServiceImpl implements UserService { }
2.3.3、回滾(rollbackFor)
設置出現哪些異常時進行事務回滾。默認情況下,事務只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾。但是我們可以設置事務在遇到特定的異常時進行回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。
spring 設置事務對哪些異常進行回滾需要在 @Transactional 注解后面添加 rollbackFor 參數,并且該參數值為需要設置的異常的 class。
2.3.4、不回滾(noRollbackFor)
設置出現哪些異常不進行事務回滾。
spring 設置事務對哪些異常進行回滾需要在 @Transactional 注解后面添加 noRollbackFor 參數,并且該參數值為需要設置的異常的 class。
3、聲明式事務管理之基于xml配置方式
通過 spring 的 xml 配置文件來實現事務管理:
<?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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!--開啟組件掃描--> <context:component-scan base-package="test, service, dao"></context:component-scan> <!--引入外部配置文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置數據庫連接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <!--通過${}使用外部配置文件的值--> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.username}"></property> <property name="password" value="${prop.password}"></property> </bean> <!-- 配置JdbcTmplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 創建事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--指定需要添加事務的方法的規則--> <tx:method name="*" propagation="REQUIRED" /> <!-- <tx:method name="transferAccount" propagation="REQUIRED" />--> </tx:attributes> </tx:advice> <!-- 配置切入點和切面 --> <aop:config> <!-- 配置切入點 --> <aop:pointcut id="pt" expression="execution(* service.UserServiceImpl.*(..))" /> <!-- 配置切面 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" /> </aop:config> </beans>

浙公網安備 33010602011771號