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

最新文章

  1. maven中classpath路径(转)
  2. [Android Pro] ScrollView使用fillViewport设置高度为MatchParent
  3. SpringMVC 数据转换 &amp; 数据格式化 &amp; 数据校验
  4. openssl使用多种方法签名、自签名
  5. loading插件(原创)
  6. JS 循环遍历JSON数据
  7. Cassandra1.2文档学习(7)—— 规划集群部署
  8. IntelliJ IDEA 使用随笔
  9. Xshell远程连接Ubuntu
  10. Handling Captcha | Webdriver
  11. JAVA POI 应用系列(1)--生成Excel
  12. Get json formatted string from web by sending HttpWebRequest and then deserialize it to get needed data
  13. mysql分组查询前n条数据
  14. ubuntu16.04开机循环输入密码无法进入桌面的解决办法
  15. Spring Boot 之使用 Json 详解
  16. lavarel 中间件
  17. winform判断一个事件是否已经绑定了事件处理函数
  18. Ubuntu中针对问题 E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable)的解决方案
  19. Spark记录-SparkSQL一些操作
  20. Some untracked working tree files would be overwritten by checkout. Please move or remove them before you can checkout. View them

热门文章

  1. 常用docker命令备忘录
  2. MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图
  3. Chisel3 - model - DefWire, Reg, Memory, Prim
  4. Second Space could let suspect play two different roles easily
  5. 分布式事务专题笔记(一) 基础概念 与 CAP 理论
  6. Ondemand和Interactive gonernor工作逻辑简述
  7. Java实现 LeetCode 767 重构字符串(ASCII的转换)
  8. Java实现 LeetCode 438 找到字符串中所有字母异位词
  9. Java实现蓝桥杯VIP算法训练 自行车停放
  10. java实现第三届蓝桥杯填算式