1. 程式人生 > 其它 >Spring事務管理

Spring事務管理

回顧事務、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 標籤設定為哪些方法配置事務,同時設定事務的傳播行為

擴充套件:事務的傳播行為有七種,先了解一下!

  1. REQUIRED:如果當前沒有事務,就建立一個新事務,如果當前存在事務,就加入該事務,該設定是最常用的設定,也是預設設定
  2. SUPPORTS:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。‘
  3. MANDATORY:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就丟擲異常。
  4. REQUIRES_NEW:建立新事務,無論當前存不存在事務,都建立新事務。
  5. NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  6. NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
  7. 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. 總結

總結一下這種方式使用事務的步驟

  1. 建立 transactionManager 開啟事務管理;
  2. 建立事務通知 tx:advice,相當於一個切面;
  3. aop:config 中用 aop:pointcut 配置切入點,由 execution 表示式設定 ;
  4. aop:advisor 把切面切入到切入點中!

當然也有其他的步驟進行配置,不過核心還是宣告式事務

Spring 就到這裡了!ヾ( ̄▽ ̄)ByeBye