zoukankan      html  css  js  c++  java
  • Spring事务管理——回滚(rollback-for)控制

    1.代码中事务控制的3种方式[1]

    • 编程式事务:就是直接在代码里手动开启事务,手动提交,手动回滚。优点就是可以灵活控制,缺点就是太麻烦了,太多重复的代码了。
    • 声明式事务:就是使用SpringAop配置事务,这种方式大大的简化了编码。需要注意的是切入点表达式一定要写正确。
    • 注解事务:直接在Service层的方法上面加上@Transactional注解,个人比较喜欢用这种方式。

    2.事务不回滚的原因

        在工作中,看过别人写的代码出现了事务不回滚的现象。当然,事务不回滚的都是采用的声明式事务或者是注解事务;编程式事务都是自己写代码手动回滚的,因此是不会出现不回滚的现象。
     
        再说下声明式事务和注解事务回滚的原理:当被切面切中或者是加了注解的方法中抛出了RuntimeException异常时,Spring会进行事务回滚。默认情况下是捕获到方法的RuntimeException异常,也就是说抛出只要属于运行时的异常(即RuntimeException及其子类)都能回滚;但当抛出一个不属于运行时异常时,事务是不会回滚的。
     
        下面说说我经常见到的3种事务不回滚的产生原因:
    • (1)声明式事务配置切入点表达式写错了,没切中Service中的方法
    • (2)Service方法中,把异常给try catch了,但catch里面只是打印了异常信息,没有手动抛出RuntimeException异常
    • (3)Service方法中,抛出的异常不属于运行时异常(如IO异常),因为Spring默认情况下是捕获到运行时异常就回滚

    代码实践

    1.不捕获异常(一般处理方式)[2]

    代码,其中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配置。

    原文出处:

    [1] zeng1994, 浅谈Spring中的事务回滚, https://www.cnblogs.com/zeng1994/p/8257763.html
    [2] JeangLee, Spring事务管理——回滚(rollback-for)控制, https://blog.csdn.net/ljyhust/article/details/73431968?locationNum=5&fps=1

  • 相关阅读:
    Ubuntu配置sublime text 3的c编译环境
    ORA-01078错误举例:SID的大写和小写错误
    linux下多进程的文件拷贝与进程相关的一些基础知识
    ASM(四) 利用Method 组件动态注入方法逻辑
    基于Redis的三种分布式爬虫策略
    Go语言并发编程总结
    POJ2406 Power Strings 【KMP】
    nyoj 会场安排问题
    Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.
    Java的String、StringBuffer和StringBuilder的区别
  • 原文地址:https://www.cnblogs.com/ryelqy/p/10104084.html
Copyright © 2011-2022 走看看