登录
首页 >  数据库 >  MySQL

完蛋,我的事务怎么不生效?

来源:SegmentFault

时间:2023-01-25 13:55:38 175浏览 收藏

本篇文章向大家介绍《完蛋,我的事务怎么不生效?》,主要包括MySQL、Java、数据库、shell、后端,具有一定的参考价值,需要的朋友可以参考一下。

  • 原子性(
    @Service("userService")
    public class UserServiceImpl implements UserService {
    
        @Resource
        UserMapper userMapper;
    
        @Autowired
        RedisUtil redisUtil;
    
        @Override
        public List getAllUsers() {
            List users = userMapper.getAllUsers();
            return users;
        }
    
        @Override
        @Transactional
        public void updateUserAge() {
            userMapper.updateUserAge(1);
            int i= 1/0;
            userMapper.updateUserAge(2);
        }
    }

    数据库操作:

    
            update user set age=age+1 where id =#{id}
        

    先获取

    java.lang.ArithmeticException: / by zero
        at com.aphysia.springdocker.service.impl.UserServiceImpl.updateUserAge(UserServiceImpl.java:35) ~[classes/:na]
        at com.aphysia.springdocker.service.impl.UserServiceImpl$$FastClassBySpringCGLIB$$c8cc4526.invoke() ~[classes/:na]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.12.jar:5.3.12]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.12.jar:5.3.12]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.12.jar:5.3.12]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.12.jar:5.3.12]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.12.jar:5.3.12]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.12.jar:5.3.12]
        at com.aphysia.springdocker.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$25070cf0.updateUserAge() ~[classes/:na]

    然后我们再次请求

    [{"id":1,"name":"李四","age":11},{"id":2,"name":"王五","age":11}]

    那什么时候事务不正常回滚呢?且听我细细道来:

    实验

    1. 引擎设置不对

    我们知道,

    mysql> show variables like 'default_storage_engine';
    +------------------------+--------+
    | Variable_name          | Value  |
    +------------------------+--------+
    | default_storage_engine | InnoDB |
    +------------------------+--------+

    那我们看看我们演示的数据表是不是也是用了

    mysql> ALTER TABLE user ENGINE=MyISAM;
    Query OK, 2 rows affected (0.06 sec)
    Records: 2  Duplicates: 0  Warnings: 0

    然后再

    [{"id":1,"name":"李四","age":12},{"id":2,"name":"王五","age":11}]

    结论:必须设置为

    [{"id":1,"name":"李四","age":12},{"id":2,"name":"王五","age":11}]

    结论:必须使用在

        public boolean rollbackOn(Throwable ex) {
            return (ex instanceof RuntimeException || ex instanceof Error);
        }

    异常主要分为以下类型:

    所有的异常都是

        @Transactional
         public void updateUserAge() throws Exception{
            userMapper.updateUserAge(1);
            try{
                int i = 1/0;
            }catch (Exception ex){
                throw new IOException("IO异常");
            }
            userMapper.updateUserAge(2);
        }

    4. 配置不对导致

    1. 方法上需要使用
      # Auto Configure
      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
      org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
      org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
      ...
      org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
      ...

      里面配置了一个

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnClass(PlatformTransactionManager.class)
      @AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
              DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
      @EnableConfigurationProperties(TransactionProperties.class)
      public class TransactionAutoConfiguration {
        ...
          @Configuration(proxyBeanMethods = false)
          @ConditionalOnBean(TransactionManager.class)
          @ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
          public static class EnableTransactionManagementConfiguration {
      
              @Configuration(proxyBeanMethods = false)
              @EnableTransactionManagement(proxyTargetClass = false)   // 这里开启了事务
              @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
              public static class JdkDynamicAutoProxyConfiguration {
      
              }
          ...
      
          }
      
      }

      值得注意的是,

          public void testTransaction(){
              updateUserAge();
          }
      
          @Transactional
           public void updateUserAge(){
              userMapper.updateUserAge(1);
              int i = 1/0;
              userMapper.updateUserAge(2);
          }

          @RequestMapping("/update")
          @ResponseBody
          public int update() throws Exception{
              userService.testTransaction();
              return 1;
          }

      调用之后,发现事务失效,一个更新另外一个没有更新:

      [{"id":1,"name":"李四","age":12},{"id":2,"name":"王五","age":11}]

      为什么会这样呢?

          @Override
          @Nullable
          public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
              AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
                      element, Transactional.class, false, false);
              if (attributes != null) {
                  return parseTransactionAnnotation(attributes);
              }
              else {
                  return null;
              }
        }

      6.多线程下事务失效

      假设我们在多线程里面像以下方式使用事务,那么事务是不能正常回滚的:

          @Transactional
          public void updateUserAge() {
              new Thread(
                      new Runnable() {
                          @Override
                          public void run() {
                              userMapper.updateUserAge(1);
                          }
                      }
              ).start();
              int i = 1 / 0;
              userMapper.updateUserAge(2);
          }

      因为不同的线程使用的是不同

      2021-11-28 14:06:59.852 DEBUG 52764 --- [       Thread-2] org.mybatis.spring.SqlSessionUtils       : Creating a new SqlSession
      2021-11-28 14:06:59.930 DEBUG 52764 --- [       Thread-2] c.a.s.mapper.UserMapper.updateUserAge    : 

      7. 注意合理使用事务嵌套

      首先事务是有传播机制的:

      • @Service("userService")
        public class UserServiceImpl {
            @Autowired
            UserServiceImpl2 userServiceImpl2;
          
            @Resource
            UserMapper userMapper;
          
              @Transactional
            public void updateUserAge() {
                try {
                    userMapper.updateUserAge(1);
                    userServiceImpl2.updateUserAge();
                }catch (Exception ex){
                    ex.printStackTrace();
                }
            }
        }

        调用的另外一个事务:

        @Service("userService2")
        public class UserServiceImpl2 {
        
            @Resource
            UserMapper userMapper;
        
            @Transactional
            public void updateUserAge() {
                userMapper.updateUserAge(2);
                int i = 1 / 0;
            }
        }

        会抛出以下错误:

        org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

        我们但是实际事务是正常回滚掉了,结果是对的,之所以出现这个问题,是因为里面到方法抛出了异常,用的是同一个事务,说明事务必须被回滚掉的,但是外层被

            @Transactional
            public void updateUserAge() {
                try {
                    userMapper.updateUserAge(1);
                    userServiceImpl2.updateUserAge();
                }catch (Exception ex){
                    ex.printStackTrace();
                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                }
            }

        8. 依赖外部网络请求回滚需要考虑

        有些时候,我们不仅操作自己的数据库,还需要同时考虑外部的请求,比如同步数据,同步失败,需要回滚掉自己的状态,在这种场景下,必须考虑网络请求是否会出错,出错如何处理,错误码是哪一个的时候才成功。

        如果网络超时了,实际上成功了,但是我们判定为没有成功,回滚掉了,可能会导致数据不一致。这种需要被调用方支持重试,重试的时候,需要支持幂等,多次调用保存状态的一致,虽然整个主流程很简单,里面的细节还是比较多的。

        image-20211128153822791

        总结

        事务被

        Spring
        包裹了复杂性,很多东西可能源码很深,我们用的时候注意模拟测试一下调用是不是能正常回滚,不能理所当然,人是会出错的,而很多时候黑盒测试根本测试这种异常数据,如果没有正常回滚,后面需要手动处理,考虑到系统之间同步的问题,会造成很多不必要的麻烦,手动改数据库这流程就必须走。

        image-20211128154248397

        【作者简介】
        秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:

        Java源码解析
        JDBC
        Mybatis
        Spring
        redis
        分布式
        剑指Offer
        LeetCode
        等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

        剑指Offer全部题解PDF

        2020年我写了什么?

        开源编程笔记

        以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于数据库的相关知识,也可关注golang学习网公众号。

声明:本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>