Spring事務管理
Spring事務管理
事務是進行資料庫操作的一個關鍵點,將 MyBatis 和 Spring 結合起來後,事務也交由 Spring 進行管理。這裡建立 Spring-11-Transaction 專案回顧之前的事務和學習 Spring 中的事務。
1. 回顧事務
事務的概念:在關係資料庫中,一個事務可以是一條 SQL 語句,一組 SQL 語句或整個程式。
事務的 ACID 特性
- 原子性( Atomicity ):事務中包括的操作要麼都做,要麼都不做。
- 一致性( Consistency ):事務必須是使資料庫從一個一致性狀態變到另一個一致性狀態。
- 隔離性( Isolation ):一個事務的執行不能被其他事務干擾。
- 永續性( Durability ):一個事務一旦提交,它對資料庫中資料的改變就應該是永久的。
在上一節專案的基礎上,回顧一下事務。把上節的專案 Copy 到本節的專案中來(略略略)。
首先給 UserMapper 介面新增新增使用者和刪除使用者的方法
public interface UserMapper {
// 查詢全部使用者
List<User> getUserList();
// 新增使用者
int addUser(User user);
// 刪除使用者
int deleteUser(int id);
}
然後在 UserMapper.xml 中配置這幾個方法要執行的 SQL 語句
<mapper namespace="com.qiyuan.dao.UserMapper"> <!--select查詢語句,使用別名記得配置 typeAlias --> <select id="getUserList" resultType="User"> select * from user </select> <!--新增使用者--> <insert id="addUser" parameterType="User"> insert into user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert> <!--刪除使用者,故意寫錯!--> <delete id="deleteUser" parameterType="int"> delete form user where id = #{id} </delete> </mapper>
注意:為了後面進行事務的測試,這裡故意把 from 寫成了 form(也是經典錯誤了),讓這個 SQL 語句執行起來報錯。
在實現類 UserMapperImpl 中封裝一下介面中的方法(這裡用的是上節的 SqlSessionTemplate 方式),同時修改一下 getUserList 方法(為了測試),讓它先新增一個使用者,再刪除該使用者,然後進行查詢,這個方法就是一組事務了!
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
// 在這裡進行封裝!
public List<User> getUserList() {
User user = new User(6, "qiyuan666", "0723");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 先新增,再刪掉,所以查詢的結果應該沒有這個使用者!
mapper.addUser(user);
mapper.deleteUser(6);
List<User> userList = mapper.getUserList();
return userList;
}
public int addUser(User user) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.addUser(user);
return row;
}
public int deleteUser(int id) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.deleteUser(id);
return row;
}
}
因為增加後又刪掉了,所以 getUserList 最後查詢到的結果應該沒有這個使用者!
本來下一步還要註冊這個實現類的,不過是 Copy 過來的配置檔案,已經註冊過了,直接進行測試
public class MyTest {
@Test
public void MyBatisSpringTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
執行測試方法的結果當然會報錯了,因為上面的 delete 語句就是錯的!
### The error occurred while setting parameters
### SQL: delete form user where id = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'user where id = 6' at line 1
由於報錯了所以查詢的方法沒執行完,所以看不到結果···我特意回去把增加和刪除的兩句話去掉,執行查詢方法看看(其實直接資料庫裡看就行)!
User(id=1, name=祈鳶, pwd=123456)
User(id=2, name=qiyuanc2, pwd=0723)
User(id=3, name=風棲祈鳶, pwd=07230723)
User(id=5, name=祈鳶bbb, pwd=123123)
User(id=6, name=qiyuan666, pwd=0723)
可以看到,即使方法執行報錯了,新增的使用者還是被寫到資料庫中了。一組事務,三件事,新增刪除查詢,只成功了第一件事,這肯定是不行的,違反了事務的 ACID 原則。
在之前單獨使用 MyBatis 的時候,可以通過在執行 mapper 的方法時捕獲異常,遇到異常就呼叫 SqlSession 的 rollback 方法回滾,執行正常就呼叫 commit 方法提交。不過這裡 SqlSession 被封裝了起來,要怎麼管理事務呢?
這就要用 Spring 實現了。
2. Spring宣告式事務
一個使用 MyBatis-Spring 的其中一個主要原因是它允許 MyBatis 參與到 Spring 的事務管理中。而不是給 MyBatis 建立一個新的專用事務管理器,MyBatis-Spring 藉助了 Spring 中的
DataSourceTransactionManager
來實現事務管理。一旦配置好了 Spring 的事務管理器,你就可以在 Spring 中按你平時的方式來配置事務。在事務處理期間,一個單獨的
SqlSession
物件將會被建立和使用。當事務完成時,這個 session 會以合適的方式提交或回滾。事務配置好了以後,MyBatis-Spring 將會透明地管理事務。這樣在你的 DAO 類中就不需要額外的程式碼了。
在 Spring 中,有兩種事務管理方式
- 宣告式事務:使用 AOP 實現,將事務管理切入到需要的地方
- 程式設計式事務:像之前一樣,在程式碼中的異常捕獲中進行對應的提交或回滾操作
程式設計式事務用起來比較麻煩,需要修改 Service 層的程式碼,用的比較少。而宣告式事務應用了 AOP,不用改變原來的程式碼,用起來更靈活。此處就只嘗試使用宣告式事務了。
2.1 標準配置
不論使用哪種方式,都要先開啟 Spring 的事務處理功能,在 Spring 的配置檔案中建立一個 DataSourceTransactionManager
物件
這裡的配置檔案是 spring-dao.xml,需要的依賴是資料來源 dataSource
<!--開啟事務管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
2.2 交由容器管理事務
開啟事務後,就可以用 AOP 進行事務的配置了(記得引入 AOP 的約束),同時還要引入事務的約束
<?xml version="1.0" encoding="UTF-8"?>
<beans ...
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="...
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/aop/spring-tx.xsd">
然後配置事務通知,tx:advice
使用上面的 transactionManager
物件進行事務管理,這就是一個切面!
<!--結合 AOP 實現事務的織入-->
<!--配置事務通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--給哪些方法配置事務-->
<!--配置事務的傳播特性-->
<tx:attributes>
<tx:method name="select" propagation="REQUIRED"/>
<!--所有方法!-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
再通過 tx:method
標籤設定為哪些方法配置事務,同時設定事務的傳播行為
擴充套件:事務的傳播行為有七種,先了解一下!
- REQUIRED:如果當前沒有事務,就建立一個新事務,如果當前存在事務,就加入該事務,該設定是最常用的設定,也是預設設定。
- SUPPORTS:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。‘
- MANDATORY:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就丟擲異常。
- REQUIRES_NEW:建立新事務,無論當前存不存在事務,都建立新事務。
- NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
- NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
- NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與 REQUIRED 類似的操作。
使用 aop:pointcut
配置切入點,再將切面切入進去!
<!--配置事務切入-->
<aop:config>
<!--配置切入點為 com.qiyuan. 下的所有包下的所有類的所有方法,不論引數!-->
<aop:pointcut id="txPointCut" expression="execution(* com.qiyuan.*.*.*(..))"/>
<!--把事務切入進去-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
這樣事務就配置完成了!到現在,一行程式碼都沒寫,只是在進行 Spring 的配置!
再執行一下測試方法(已經把之前錯誤插入的 6 號使用者先刪了),當然還是會報錯的!
true
[User(id=1, name=祈鳶, pwd=123456),
User(id=2, name=qiyuanc2, pwd=0723),
User(id=3, name=風棲祈鳶, pwd=07230723),
User(id=5, name=祈鳶bbb, pwd=123123)]
不過可以看到沒有 6 號使用者了!說明事務回滾了!
Debug:遇到了一個 bug 導致事務沒成功,找了半天。上面的 true 是由
System.out.println(TransactionSynchronizationManager.isActualTransactionActive());
輸出的,可以判斷本方法是否在事務管理中。一開始一直是 false,捏麻麻的找了半天發現是 execution 表示式寫錯了!
之前寫的表示式是 ↓,代表的是 com.qiyuan 包下的所有類的所有方法!少了一級!導致切入點錯了!
<aop:pointcut id="txPointCut" expression="execution(* com.qiyuan.*.*(..))"/>
改成 ↓ 後,就成功了!代表的是 com.qiyuan 下所有包中的所有類的所有方法!
<aop:pointcut id="txPointCut" expression="execution(* com.qiyuan.*.*.*(..))"/>
暫且認為這個表示式是從後往前推的!
3. 總結
總結一下這種方式使用事務的步驟
- 建立
transactionManager
開啟事務管理; - 建立事務通知
tx:advice
,相當於一個切面; - 在
aop:config
中用aop:pointcut
配置切入點,由 execution 表示式設定 ; - 用
aop:advisor
把切面切入到切入點中!
當然也有其他的步驟進行配置,不過核心還是宣告式事務!
Spring 就到這裡了!ヾ( ̄▽ ̄)ByeBye