• 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配置。

    转自:https://blog.csdn.net/ljyhust/article/details/73431968?locationNum=5&fps=1

  • 相关阅读:
    EF6 在原有数据库中使用 CodeFirst 总复习(三、重建迁移)
    EF6 在原有数据库中使用 CodeFirst 总复习(四、新建实体对象)
    EF6 在原有数据库中使用 CodeFirst 总复习(五、生成发帖页面)
    实体框架 (EF) 入门 => 一、我该用哪个工作流?
    实体框架 (EF) 入门 => 二、在全新的数据库中使用 Code First
    asp.net core 2.0 webapi集成signalr
    实体框架 (EF) 入门 => 三、CodeFirst 支持的完整特性列表
    ORM框架之------Dapper,Net下无敌的ORM
    Dapper Helper
    .NET平台微服务项目汇集
  • 原文地址:https://www.cnblogs.com/toSeeMyDream/p/9538008.html
Copyright © 2020-2023  润新知