菜瓜:上次的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的锁

【Spring】@Transactional 闲聊的更多相关文章

  1. 数据库事务中的隔离级别和锁+spring Transactional注解

    数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...

  2. How does Spring @Transactional Really Work?--转

    原文地址:http://blog.jhades.org/how-does-spring-transactional-really-work/ In this post we will do a dee ...

  3. Spring @Transactional使用的示例

    Spring @Transactional使用的示例: 参考: http://blog.csdn.net/seng3018/article/details/6690527 http://blog.si ...

  4. Spring @Transactional 使用

    Spring @Transactional是Spring提供的一个声明式事务,对代码的侵入性比较小,只需考虑业务逻辑,不需要把事务和业务搞混在一起. @Transactional 可以注解在inter ...

  5. Java:Spring @Transactional工作原理

    本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: propagation(事务传播)和isolation(隔离性)等属性的使用 事务使用 ...

  6. spring @Transactional 事务注解

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE, rollbackFor = ...

  7. Spring @Transactional (一)

    Spring @Transactional (一) 博客分类: JAVA SpringJPAJDBCUPSQL  Spring事务的传播行为 在service类前加上@Transactional,声明 ...

  8. [转]数据库事务中的隔离级别和锁+spring Transactional注解

    数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...

  9. 25.Spring @Transactional工作原理

    转自:http://www.importnew.com/12300.html 本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: prop ...

随机推荐

  1. burpsuite 2.0beta体验

    这里有破解版:http://ximcx.cn/post-110.html 一直再用1.7x版本,2.0的还没怎么用过 移除了 Scanner 和spider 选项卡,全部整理到Dashboard里 代 ...

  2. matlab单目相机标定——标定步骤以及参数含义

    参考博客园的一篇文章: https://www.cnblogs.com/flyinggod/p/8470407.html#commentform

  3. 认证(Authentication)和授权(Authorization)总结

    身份认证是验证你的身份,一旦通过验证,即启用授权.你所拥有的身份可以进行哪些操作都是由授权规定.例如,任何银行客户都可以创建一个账户(如用户名),并使用该账户登录该银行的网上服务,但银行的授权政策必须 ...

  4. 01 . Nginx简介及部署

    Nginx简介 Nginx(发音同engine x)是一个异步框架的 Web 服务器,也可以用作反向代理,负载平衡器 和 HTTP 缓存.该软件由 Igor Sysoev 创建,并于2004年首次公开 ...

  5. Java实现 LeetCode 777 在LR字符串中交换相邻字符(分析题)

    777. 在LR字符串中交换相邻字符 在一个由 'L' , 'R' 和 'X' 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作.一次移动操作指用一个"L ...

  6. Java实现 LeetCode 658 找到 K 个最接近的元素(暴力)

    658. 找到 K 个最接近的元素 给定一个排序好的数组,两个整数 k 和 x,从数组中找到最靠近 x(两数之差最小)的 k 个数.返回的结果必须要是按升序排好的.如果有两个数与 x 的差值一样,优先 ...

  7. Java实现 蓝桥杯VIP 算法训练 Hanoi问题

    问题描述 如果将课本上的Hanoi塔问题稍做修改:仍然是给定N只盘子,3根柱子,但是允许每次最多移动相邻的M只盘子(当然移动盘子的数目也可以小于M),最少需要多少次? 例如N=5,M=2时,可以分别将 ...

  8. Java实现第十届蓝桥杯数的分解

    试题 D: 数的分解 本题总分:10 分 [问题描述] 把 2019 分解成 3 个各不相同的正整数之和,并且要求每个正整数都不包 含数字 2 和 4,一共有多少种不同的分解方法? 注意交换 3 个整 ...

  9. Java实现第八届蓝桥杯外星日历

    外星日历 题目描述 某星系深处发现了文明遗迹. 他们的计数也是用十进制. 他们的文明也有日历.日历只有天数,没有年.月的概念. 有趣的是,他们也使用了类似"星期"的概念, 只不过他 ...

  10. java实现第六届蓝桥杯机器人数目

    机器人数目 少年宫新近邮购了小机器人配件,共有3类,其中, A类含有:8个轮子,1个传感器 B类含有: 6个轮子,3个传感器 C类含有:4个轮子,4个传感器 他们一共订购了100套机器人,收到了轮子6 ...