Spring事務管理——回滾(rollback-for)控制

探討Spring事務控制中,異常觸發事務回滾原理。文章進行了6種情況下的Spring事務是否回滾。
以下代碼都是基於Spring與Mybatis整合,使用Spring聲明式事務配置事務方法。

1.不捕獲異常(一般處理方式)

代碼,其中contentMappger.updateWithErrTest(31L); 是SQL語句錯誤,用來測試回滾。

    /**
     * 刪除多條記錄
     */
    @Override
    public ShopResult deleteContentGroup(String[] ids) {
        if (null == ids || ids.length == 0)
        {
            return ShopResult.error();
        }
        for (String idStr : ids)
        {
            Long id = new Long(idStr);
            contentMappger.deleteByPrimaryKey(id);
        }
        contentMappger.updateWithErrTest(31L);   //錯誤代碼,SQL語句錯誤。用來測試事務,看是否回滾
        return ShopResult.ok();
    }

運行結果:報錯,事務發生了回滾,即由於錯誤代碼,前面的for循環刪除記錄事務被回滾了。

### SQL: delete form tb_content    where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1
; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
    at com.mysql.jdbc.Util.getInstance(Util.java:381)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1030)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3536)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3468)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1957)
    ..................
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)
    at com.sun.proxy.$Proxy35.updateWithErrTest(Unknown Source)
    at com.shop.manager.service.impl.ContentServiceImpl.deleteContentGroup(ContentServiceImpl.java:94)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    .......

2. 捕獲異常,但不處理,不拋出

代碼

    /**
     * 刪除多條記錄
     */
    @Override
    public ShopResult deleteContentGroup(String[] ids) {
        if (null == ids || ids.length == 0)
        {
            return ShopResult.error();
        }
        for (String idStr : ids)
        {
            Long id = new Long(idStr);
            contentMappger.deleteByPrimaryKey(id);
        }
        try {
            contentMappger.updateWithErrTest(31L);   //錯誤代碼,SQL語句錯誤。用來測試事務,看是否回滾
        } catch (Exception e) {
            //捕獲異常,但不處理
            System.out.println("-----nothing to do-------");
        }
        return ShopResult.ok();
    }

運行結果:事務提交,未回滾。
事務提交

### The error occurred while setting parameters
### SQL: delete form tb_content    where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1
]
-----nothing to do-------
2017-06-18 14:27:59,493 [http-bio-8080-exec-4] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization committing SqlSession //(事務提交)
[org.apache.ibatis.session.defaults.DefaultSqlSession@616a85a9]

3. 捕獲異常,並拋出RuntimeException異常

Spring碰到Unchecked Exceptions都會回滾,不僅是RuntimeException,也包括Error。
代碼

    /**
     * 刪除多條記錄
     */
    @Override
    public ShopResult deleteContentGroup(String[] ids) {
        if (null == ids || ids.length == 0)
        {
            return ShopResult.error();
        }
        for (String idStr : ids)
        {
            Long id = new Long(idStr);
            contentMappger.deleteByPrimaryKey(id);
        }
        try {
            contentMappger.updateWithErrTest(31L);   //錯誤代碼,SQL語句錯誤。用來測試事務,看是否回滾
        } catch (Exception e) {
            System.out.println("----throw Exception-----");
            throw new RuntimeException();
        }
        return ShopResult.ok();
    }

運行結果:如預期的一樣,拋出RuntimeException,事務發生回滾。

### SQL: delete form tb_content    where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1
]
----throw Exception-----
2017-06-18 14:21:27,928 [http-bio-8080-exec-1] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3ef56e3a]
...............
2017-06-18 14:21:27,941 [http-bio-8080-exec-1] [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]-[DEBUG] Resolving exception from handler [public com.shop.common.pojo.ShopResult com.shop.manager.controller.ContentController.deleteContentGroup(java.lang.String)]: java.lang.RuntimeException
2017-06-18 14:21:27,941 [http-bio-8080-exec-1] [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]-[DEBUG] Resolving exception from handler [public com.shop.common.pojo.ShopResult com.shop.manager.controller.ContentController.deleteContentGroup(java.lang.String)]: java.lang.RuntimeException
2017-06-18 14:21:27,942 [http-bio-8080-exec-1] [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]-[DEBUG] Resolving exception from handler [public com.shop.common.pojo.ShopResult com.shop.manager.controller.ContentController.deleteContentGroup(java.lang.String)]: java.lang.RuntimeException
2017-06-18 14:21:27,942 [http-bio-8080-exec-1] [org.springframework.web.servlet.DispatcherServlet]-[DEBUG] Could not complete request
java.lang.RuntimeException
    at com.shop.manager.service.impl.ContentServiceImpl.deleteContentGroup(ContentServiceImpl.java:98)  //異常
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

4.捕獲異常,並繼續拋出原捕獲的異常

代碼:

    /**
     * 刪除多條記錄
     */
    @Override
    public ShopResult deleteContentGroup(String[] ids) {
        if (null == ids || ids.length == 0)
        {
            return ShopResult.error();
        }
        for (String idStr : ids)
        {
            Long id = new Long(idStr);
            contentMappger.deleteByPrimaryKey(id);
        }
        try {
            contentMappger.updateWithErrTest(31L);   //錯誤代碼,SQL語句錯誤。用來測試事務,看是否回滾
        } catch (Exception e) {
            //捕獲異常,繼續拋出
            System.out.println("-----throw Exception-------");
            throw e;
        }
        return ShopResult.ok();
    }

運行結果:拋出異常,事務發生回滾

### The error occurred while setting parameters
### SQL: delete form tb_content    where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1
]
-----throw Exception-------
2017-06-18 14:36:25,308 [http-bio-8080-exec-9] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@45fe0f70]
2017-06-18 14:36:25,308 [http-bio-8080-exec-9] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization closing SqlSession //事務回滾
[org.apache.ibatis.session.defaults.DefaultSqlSession@45fe0f70]

5. 捕獲異常,並拋出新new的異常(或自定義Exception異常) new Exception

代碼:

    /**
     * 刪除多條記錄
     * @throws Exception 
     */
    @Override
    public ShopResult deleteContentGroup(String[] ids) throws Exception {
        if (null == ids || ids.length == 0)
        {
            return ShopResult.error();
        }
        for (String idStr : ids)
        {
            Long id = new Long(idStr);
            contentMappger.deleteByPrimaryKey(id);
        }
        try {
            contentMappger.updateWithErrTest(31L);   //錯誤代碼,SQL語句錯誤。用來測試事務,看是否回滾
        } catch (Exception e) {
            //捕獲異常,拋出新異常
            System.out.println("-----throw new Exception(e)-------");
            throw new Exception(e);
        }       
        return ShopResult.ok();
    }

運行結果:事務提交,未回滾。(Spring的默認回滾異常類型不包括Exception)

### The error occurred while setting parameters
### SQL: delete form tb_content    where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1
]
-----throw new Exception(e) -------
2017-06-18 14:43:16,098 [http-bio-8080-exec-10] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization committing SqlSession //事務提交
[org.apache.ibatis.session.defaults.DefaultSqlSession@32c4821]
2017-06-18 14:43:16,098 [http-bio-8080-exec-10] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32c4821]

6. 在事務配置中沒有設置rollback-for異常類型爲Exception

<!-- 事務管理器 -->
    <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="save" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="insert*" propagation="REQUIRED"  rollback-for="Exception"/>
            <tx:method name="add*" propagation="REQUIRED"  rollback-for="Exception"/>
            <tx:method name="create*" propagation="REQUIRED"  rollback-for="Exception"/>
            <tx:method name="delete*" propagation="REQUIRED"  rollback-for="Exception"/>
            <tx:method name="update*" propagation="REQUIRED"  rollback-for="Exception"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>
    <!-- 配置切面 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.shop.manager.service.*.*(..))" />
    </aop:config>
    /**
     * 刪除多條記錄
     * @throws Exception 
     */
    @Override
    public ShopResult deleteContentGroup(String[] ids) throws Exception {
        if (null == ids || ids.length == 0)
        {
            return ShopResult.error();
        }
        for (String idStr : ids)
        {
            Long id = new Long(idStr);
            contentMappger.deleteByPrimaryKey(id);
        }
        try {
            contentMappger.updateWithErrTest(31L);   //錯誤代碼,SQL語句錯誤。用來測試事務,看是否回滾
        } catch (Exception e) {
            //捕獲異常,繼續拋出
            System.out.println("-----throw new Exception-------");
            throw new Exception("---自定義Exception,事務中已配置rollback-for---");
        }
        return ShopResult.ok();
    }

運行結果:如預期一樣發生回滾

### The error may involve com.shop.manager.mapper.TbContentMapper.updateWithErrTest-Inline
### The error occurred while setting parameters
### SQL: delete form tb_content    where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'tb_content
    where kid = 31' at line 1
]
-----throw new Exception-------
2017-06-18 15:07:02,273 [http-bio-8080-exec-8] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f177061]
2017-06-18 15:07:02,273 [http-bio-8080-exec-8] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f177061]

總結:

  1. Spring事務管理是根據異常來進行回滾操作;
  2. Spring與Mybatis整合時,雖然在Service方法中並沒有check異常,但是如果數據庫有異常發生,默認會進行事務回滾。
  3. Spring 如果不添加rollbackFor等屬性,Spring碰到Unchecked Exceptions都會回滾,不僅是RuntimeException,也包括Error。
  4. 如果在事務方法中捕獲異常並進行處理,一定要繼續拋出異常並在Spring事務管理中進行rollbak-for配置。
發佈了29 篇原創文章 · 獲贊 24 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章