• 【Spring】@Transactional 闲聊


    菜瓜:上次的AOP理论知识看完收获挺多的,虽然有一个自定义注解的demo,但还是觉得差点东西

    水稻:我也觉得没有跟一遍源码还是差点意思,这次结合@Transactional注解深入源码看一下

    菜瓜:事务注解,这个平时用的挺多的

    水稻:是吗?来看看你的基础咋样

    1. 要保证一个方法中多个数据库操作的原子性,要共用一个数据库连接,但是coding时我们不用显示传递连接对象,这是咋弄的?
    2. 如果一个方法里面只有查询操作,是否不用开启事务?
    3. 如何解决非事务方法调用本地事务方法失效的?
    4. 注解常用的传播属性,你知道他们的区别吗

    菜瓜:虽然没看过源码,我大胆猜测一下

    1. 隐式传递连接对象可以将其封装到线程中,一般一次请求操作都是在一个线程中完成。使用ThreadLocal将连接和线程绑定
    2. 查询操作也得看业务场景,如果多次查询相同的数据要避免不可重复读问题,可开启只读事务 (readOnly = true)
    3. 结合AOP的知识,这里其实要解决调用事务方法的对象不是代理对象的问题。用代理对象调本地事务方法即可(注入自己)
      • /**
         * @author QuCheng on 2020/6/24.
         */
        @Service
        public class ItemServiceImpl implements ItemService {
        
            @Resource
            private IcbcItemMapper itemMapper;
        
            @Resource
            private ItemService itemService;
        
            @Override
            public void changeNameById(Long itemId) {
                // changeItemById(itemId);
                itemService.changeItemById(itemId);
            }
        
            @Transactional(rollbackFor = RuntimeException.class)
            @Override
            public void changeItemById(Long itemId) {
                itemMapper.updateNameById(itemId, "name4");
                int a = 10 / 0;
                itemMapper.updatePriceById(itemId, 100L);
            }
        }
    4. 传播属性这个没了解过啊,数据库事务里面么得这个概念

    水稻:可以啊,平时的代码没白写

    菜瓜:coding这种事情,easy啦!

    水稻:这就飘了?来看这个问题

    • 如果我想在A事务方法中调用B事务方法,B方法如果回滚了,不能影响A事务继续执行,但是A事务如果执行出问题了,B也要回滚,怎么弄?

    菜瓜:。。。这不就是大事务嵌套小事务嘛。。。我不会

    水稻:不扯了,来看源码吧,这个问题等解释了传播属性你就知道了

    • 上回我们说到,@Transactional是AOP的典型应用,bean被实例化之后要创建代理(参考自定义注解),就少不了切面类Advisor对象。那么它是谁,它在哪,它在干什么?
    • 回到事务功能开启的注解@EnableTransactionManagement
      • 没错,它肯定会有一个Import注解引入TransactionManagementConfigurationSelector类
      • public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
         
           @Override
           protected String[] selectImports(AdviceMode adviceMode) {
              switch (adviceMode) {
                 case PROXY:
                    return new String[] {AutoProxyRegistrar.class.getName(),
                          // 看这里
                          ProxyTransactionManagementConfiguration.class.getName()};
                 case ASPECTJ:
                    return new String[] {determineTransactionAspectClass()};
                 default:
                    return null;
              }
           }
        。。。
        
        }
        
        
        @Configuration
        public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
        
           @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
           @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
           public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
              BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
              advisor.setTransactionAttributeSource(transactionAttributeSource());
              advisor.setAdvice(transactionInterceptor());
              if (this.enableTx != null) {
                 advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
              }
              return advisor;
           }
        
           @Bean
           @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
           public TransactionAttributeSource transactionAttributeSource() {
              return new AnnotationTransactionAttributeSource();
           }
        
           @Bean
           @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
           public TransactionInterceptor transactionInterceptor() {
        // 增强 TransactionInterceptor interceptor
        = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource()); if (this.txManager != null) { interceptor.setTransactionManager(this.txManager); } return interceptor; } } 
      • 切面类对象设置了事务的扫描器,也set了增强类TransactionInterceptor
      • public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
        。。。
        @Override
            @Nullable
            public Object invoke(MethodInvocation invocation) throws Throwable {
                // Work out the target class: may be {@code null}.
                // The TransactionAttributeSource should be passed the target class
                // as well as the method, which may be from an interface.
                Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        
                // Adapt to TransactionAspectSupport's invokeWithinTransaction...
                return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
            }
        }
        
        public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
          
        。。。  
        @Nullable
        protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
              final InvocationCallback invocation) throws Throwable {
           。。。
        // ①创建事务,数据库连接处理也在这 TransactionInfo txInfo
        = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try {
        // 调用目标方法 retVal
        = invocation.proceedWithInvocation(); } catch (Throwable ex) {
        // 异常后事务处理 completeTransactionAfterThrowing(txInfo, ex);
        throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } 。。。 } 

    菜瓜:懂,接下来的代码逻辑就是在增强类TransactionInterceptor的invoke方法里

    水稻:对

    • 先看数据库连接的处理 - 验证ThreadLocal
    • protected void doBegin(Object transaction, TransactionDefinition definition) {
         。。。
      // 如果连接是新的,就进行绑定
      if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder()); } 。。。 } class TransactionSynchronizationManager public static void bindResource(Object key, Object value) throws IllegalStateException { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Assert.notNull(value, "Value must not be null"); Map<Object, Object> map = resources.get(); // set ThreadLocal Map if none found if (map == null) { map = new HashMap<>(); resources.set(map); } Object oldValue = map.put(actualKey, value); // Transparently suppress a ResourceHolder that was marked as void... if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { oldValue = null; } if (oldValue != null) { throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); } if (logger.isTraceEnabled()) { logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]"); } }
    • 回过头来看AB方法调用的回滚问题,直接给出答案(突然发现这个问题要讲清楚篇幅会很大,就。。挺突然的。。挺突然der)
      • 在B方法上设置传播属性为NESTED即可,然后在A中catch住B的异常
      • 你肯定会问不加NESTED去catch不行吗?不行,非NESTED的方法抛出的异常是无法回滚的。
      • 不信你看
      • @Transactional(rollbackFor = RuntimeException.class)
            @Override
            public void changeNameById(Long itemId) {
                itemMapper.updateNameById(itemId, "A");
                try {
                    itemService.changeItemById(itemId);
        //            itemService.changeItemByIdNested(itemId);
                } catch (Exception e) {
                    System.out.println("我想继续执行,不影响修改A");
                }
                itemMapper.updatePriceById(itemId, 1L);
            }
        
            @Transactional(rollbackFor = RuntimeException.class)
            @Override
            public void changeItemById(Long itemId) {
                itemMapper.updateNameById(itemId, "B+REQUIRED");
                itemMapper.updatePriceById(itemId, 10L);
                int a = 10 / 0;
            }
        
            @Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.NESTED)
            @Override
            public void changeItemByIdNested(Long itemId) {
                itemMapper.updateNameById(itemId, "B+NESTED");
                itemMapper.updatePriceById(itemId, 100L);
                int a = 10 / 0;
            }
        
        
        -- 测试结果
        //①  itemService.changeItemById(itemId);  数据库所有数据都不会改变
        我想继续执行,不影响修改A
        org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
        
        // ② itemService.changeItemByIdNested(itemId); 第一个方法的修改会生效
        我想继续执行,不影响修改A

    菜瓜:这就是传播属性NESTED?默认的是REQUIRED,还有一个常用的REQUIRES_NEW呢?

    水稻:搞清楚这个其实从数据库连接入手其实就很清楚

    • REQUIRED修饰的方法和A使用同一个连接,A和B是挂一起的,谁回滚都会影响对方,且B方法的异常会被事务管理器标记为必须回滚
    • NESTED修饰的方法和A使用同一个连接,但是用到了数据库的savePoint特性,它可以回滚到指定的点,如果是有回滚点的操作,抛出的异常可以被处理
    • REQUIRES_NEW修饰的方法和A使用的就不是一个连接了,回不回滚都不会影响对方,当然,要捕捉异常

    菜瓜:传播属性了解。回滚的问题还得再看看,篇幅很大是很复杂吗?

    水稻:其实不复杂,就是要跟踪源码断点调试。。。截图搞来搞去,篇幅就很长,你自己去调的话其实很快

    菜瓜:那我下去康康

    总结

    • 这里提到Transactional注解其实是为了巩固AOP的,当然提到了一些注意点。譬如本地调用,譬如ThreadLocal的应用,还譬如传播属性
    • 传播属性其实用的少,但是聊起来就比较多了,可以聊事务的隔离级别,聊ACID的实现,聊MySQL的锁
  • 相关阅读:
    BZOJ 1597 [Usaco2008 Mar]土地购买 (斜率优化dp)
    HDU 6602 Longest Subarray (线段树)
    HDU 6521 K-th Closest Distance (主席树+二分)
    2019牛客多校2 H Second Large Rectangle(悬线法)
    The 2019 University of Jordan Collegiate Programming Contest
    CLR via C# 阅读 笔记
    C# 访问https 未能创建 SSL/TLS 安全通道
    转载文章——Datatable删除行的Delete和Remove方法
    ASP.NET Request.UrlReferrer 问题
    ASP.NET WebMethod方法使用 、AngularJS $http请求、 Jquery $.ajax请求
  • 原文地址:https://www.cnblogs.com/nightOfStreet/p/13192214.html
Copyright © 2020-2023  润新知