SpringBoot中事务执行原理分析(一)

简介: SpringBoot中事务执行原理分析(一)

从本文开始我们开始分析事务执行原理,本文的环境是SpringBoot(+MybatisPlus),当然在SSM的环境下其本质也是一致的。


【1】两种环境下测试save

① 非事务环境下的save

如下所示,如果没有应用事务(比如事务注解或者xml配置或者编程式事务),那么通常在PreparedStatement执行后,数据就进入了数据库,即使下一行业务代码抛出了异常也不会导致回滚。

PreparedStatementHandler的update方法。

这里得到的PreparedStatement对象是个代理其真实对象中维护的connection的isAutoCommit=true,也就是自动提交。

s.execute();方法将会触发入库动作的执行,数据将会插入数据库。

插入完数据后依次返回,如下所示在SqlSessionInterceptor的invoke方法这里会执行sqlSession.commit(true);其最终会触发 transaction.commit();也就是提交事务。

② 有事务环境下的save

如下所示,我们在接口层给方法加上事务注解。

public interface SysAdviceService extends IService<SysAdvice> {
    @Transactional(rollbackFor = Exception.class)
    boolean testSave(SysAdvice sysAdvice);
}

这里得到的PreparedStatement对象是个代理其真实对象中维护的connection的isAutoCommit=false,也就是不自动提交

与上个环境不同的是当ps.execute();执行完,这里数据并没有插入。

在SqlSessionInterceptor的invoke方法中,这里也没有触发sqlSession.commit(true);

那么事务在什么时候提交的呢?数据在什么时候入库的呢?如下所示在TransactionAspectSupport的invokeWithinTransaction方法中commitTransactionAfterReturning(txInfo);将会执行事务提交将数据插入数据库。

670a4f7da6b4418bb8f0d53bca7e2de8.png

③ 事务环境下抛出异常

如下所示,我们在service方法中加入异常代码。

@Override
public boolean testSave(SysAdvice sysAdvice) {
    adviceMapper.insert(sysAdvice);
    System.out.println(1/0);
    return false;
}

如下所示,这里TransactionAspectSupport的invokeWithinTransaction方法将会抛出异常。那么将不会触发commitTransactionAfterReturning(txInfo);进行事务提交插入数据。

继续往下跟踪,TransactionAspectSupport(实际当前对象是其子类TransactionInterceptor)的completeTransactionAfterThrowing方法中我们能够看到这里触发了回滚。


从上面的实例中我们看到了TransactionAspectSupport的身影,在有事务环境下该类来触发提交事务或者回滚的动作。那么TransactionAspectSupport是什么,从哪里来?

【2】TransactionAspectSupport

TransactionAspectSupport是事务切面的基础类,事务切面比如TransactionInterceptor。实际上TransactionInterceptor确实为TransactionAspectSupport的子类。


这使得底层Spring事务基础设施可以轻松地用于实现任何切面系统的切面。子类负责按正确的顺序调用此类中的方法。这里 应用了策略设计模式。PlatformTransactionManager 或ReactiveTransactionManager 实现将执行实际的事务管理,而 TransactionAttributeSource(例如基于注解的事务)用于确定特定类或方法的事务定义。


我们继续看TransactionInterceptor。如下所示TransactionInterceptor继承自TransactionAspectSupport 实现了MethodInterceptor接口。

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
  //...
}

也就是说起在父类TransactionAspectSupport基础了,实现了MethodInterceptor接口,提供了invoke方法供其他地方调用。其实熟悉AOP的同学这里就能过联想到,实现了MethodInterceptor接口的大多被作为Advice使用。


如下所示当触发SysAdviceServiceImpl的testSave方法时,实际是触发了代理类然后交给了CglibAopProxy.DynamicAdvisedInterceptor的DynamicAdvisedInterceptor方法。

e0f2d5469dd645cc8e7cb9cdfcd5e2cb.png

如下所示最终会触发TransactionInterceptor的invoke方法。这里我们暂时跳过事务实现的详情,可以得出结论:SpringBoot下的事务执行原理是由代理实现的!

从前面我们也能够看到,在代理执行过程中,这里我们的advisor是BeanFactoryTransactionAttributeSourceAdvisor。


那么什么是BeanFactoryTransactionAttributeSourceAdvisor,什么时候实例化的,如何对本文这里的SysAdviceServiceImpl进行了代理包装?

【3】BeanFactoryTransactionAttributeSourceAdvisor

看到Advisor第一反应就该是增强器/顾问器,就要联想到AOP、切面、切入点、增强通知、代理。


BeanFactoryTransactionAttributeSourceAdvisor 由 TransactionAttributeSource 驱动,用于包含事务性方法的事务通知bean(比如你的方法上标注了@Transactional注解)。如下所示其继承自AbstractBeanFactoryPointcutAdvisor维护了transactionAttributeSource、pointcut 、advice等成员信息。

public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
  @Nullable
  private TransactionAttributeSource transactionAttributeSource;
  // 这个很有意思,是个final,且直接实例化赋值了
  private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
    @Override
    @Nullable
    protected TransactionAttributeSource getTransactionAttributeSource() {
      return transactionAttributeSource;
    }
  };
  //...
}

那么第一个问题,这个Advisor是在什么时候实例化的?

① BeanFactoryTransactionAttributeSourceAdvisor 的注册和实例化

如下所示,在配置类ProxyTransactionManagementConfiguration 中注册了BeanFactoryTransactionAttributeSourceAdvisor 、TransactionAttributeSource、以及TransactionInterceptor。并为transactionAdvisor设置advice属性,值为transactionInterceptor。

@Configuration(proxyBeanMethods = false)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
  @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
      TransactionAttributeSource transactionAttributeSource,
      TransactionInterceptor transactionInterceptor) {
    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(
      TransactionAttributeSource transactionAttributeSource) {
    TransactionInterceptor interceptor = new TransactionInterceptor();
    interceptor.setTransactionAttributeSource(transactionAttributeSource);
    if (this.txManager != null) {
      interceptor.setTransactionManager(this.txManager);
    }
    return interceptor;
  }
}

bd75382414de471a97b7c53b85bd2f28.png

这个类总结来讲就是注册启用基于代理的注解驱动的事务管理所需的Spring基础结构bean。

那么第二个问题,ProxyTransactionManagementConfiguration 是如何注册实例化的?

② ProxyTransactionManagementConfiguration

如下所示,在TransactionManagementConfigurationSelectorselectImports方法中,“注册了”AutoProxyRegistrarProxyTransactionManagementConfiguration

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
// 注意这个selectImports方法哦 
  @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;
    }
  }
  private String determineTransactionAspectClass() {
    return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
        TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
        TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
  }
}

TransactionManagementConfigurationSelector是一个ImportSelector,在SpringBoot自动配置原理解析一文中我们分析过,在应用上下文刷新过程中,会使用ConfigurationClassPostProcessor扫描并注册我们的配置类(和相关类)。


如下所示,我们自定义的MybatisPlusConfig 标注了@EnableTransactionManagement注解。

@EnableTransactionManagement
@MapperScan({"com.recommend.mapper"})
@Configuration
public class MybatisPlusConfig {
  //...
}

而@EnableTransactionManagement注解注解导入了TransactionManagementConfigurationSelector,其selectImports又引入了AutoProxyRegistrar和ProxyTransactionManagementConfiguration。

64027e7ca2db4936940d3a5d25a2a51a.png

而在处理ProxyTransactionManagementConfiguration的过程中又会注入注册了BeanFactoryTransactionAttributeSourceAdvisor 、TransactionAttributeSource、以及TransactionInterceptor。并为transactionAdvisor设置advice属性,值为transactionInterceptor。

这些Bean在ConfigurationClassPostProcessor中都会被注册了BeanDefinition,并在Spring中refresh分析之finishBeanFactoryInitialization方法详解被实例化。

③ 如何为我们的service进行代理包装?

其实换个说法就是如何为bean创建代理,这个具体过程我们在Spring AOP中如何为Bean创建代理?有过详细分析。我们这里简单看下如何为service包装代理。


如下所示,这里同样来到了AbstractAutoProxyCreator尝试为SysAdviceServiceImpl对象创建代理。

image.png

在wrapIfNecessary方法中会尝试获取当前bean的"拦截器"(没错,从业务概念上讲我们可以称之为拦截器),如果specificInterceptors不为空,则为当前bean实例创建代理。


如下所示,这里我们获取到了BeanFactoryTransactionAttributeSourceAdvisor,那么将会为我们的bean创建代理。

这里需要说明一点,首先从容器中获取到Advisor后,还会多进一步筛选。判断当前Advisor与目标类是否匹配。以BeanFactoryTransactionAttributeSourceAdvisor为例,这里会判断方法是否为public、方法上(当前类或者接口层)是否有事务注解信息。


关于Advisor与service匹配更多信息可以参考博文:SpringBoot中事务执行原理分析补充篇544e73da8a0846ccb6d0302d8674ee0a.png

在DefaultAopProxyFactory的createAopProxy方法中会进行判断决定是JdkDynamicAopProxy还是ObjenesisCglibAopProxy。本文这里TargetClass是实际的class com.recommend.service.impl.SysAdviceServiceImpl,将会触发cglib代理。1fb4df983e7b40869f6278f698c5a556.png

这里我们得到的代理对象如下所示:

④ 为什么是CGLIB代理?

在SSM环境下,如果我们定义了@EnableAspectJAutoProxy(proxyTargetClass = false),那么对于我们实现了接口的service来讲是触发JDK动态代理的。但是在SpringBoot 2.2.4.RELEASE版本下,由于AOP的自动配置,其强制将proxyTargetClass 设置为了true。


当我们尝试进行代理时,我们拿到的AnnotationAwareAspectJAutoProxyCreator对象中,proxyTargetClass 属性是true。也就是说假设在主启动类配置了@EnableAspectJAutoProxy(proxyTargetClass = false),SpringBoot会忽略你的false配置。


关于这个原理,可以参考AopAutoConfiguration,这个配置类中的CglibAutoProxyConfiguration默认采用proxyTargetClass = true效果。

ffaaa6a6f2d043e59c601734a98de925.png


目录
相关文章
|
3月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
64 0
|
3月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
162 0
|
3月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
41 0
|
3月前
|
Java Spring
SpringBoot自动配置原理
本文深入解析了SpringBoot的核心功能——自动配置,重点探讨了`org.springframework.boot.autoconfigure`及相关注解的工作机制。通过分析`@SpringBootApplication`、`@EnableAutoConfiguration`等注解,揭示了SpringBoot如何基于类路径和条件自动装配Bean
123 7
|
3月前
|
Java
SpringBoot自动装配的原理
在SpringBoot项目的启动引导类上都有一个注解@SpringBootApplication 这个注解是一个复合注解, 其中有三个注解构成 , 分别是 ● @SpringBootConfiguration : 是@Configuration的派生注解 , 标注当前类是一个SpringBoot的配置类 ● @ComponentScan : 开启组件扫描, 默认扫描的是当前启动引导了所在包以及子包 ● @EnableAutoConfiguration : 开启自动配置(自动配置核心注解) 2.在@EnableAutoConfiguration注解的内容使用@Import注解导入了一个AutoC
|
2月前
|
SQL 前端开发 Java
深入分析 Spring Boot 项目开发中的常见问题与解决方案
本文深入分析了Spring Boot项目开发中的常见问题与解决方案,涵盖视图路径冲突(Circular View Path)、ECharts图表数据异常及SQL唯一约束冲突等典型场景。通过实际案例剖析问题成因,并提供具体解决方法,如优化视图解析器配置、改进数据查询逻辑以及合理使用外键约束。同时复习了Spring MVC视图解析原理与数据库完整性知识,强调细节处理和数据验证的重要性,为开发者提供实用参考。
103 0
|
2月前
|
安全 前端开发 Java
Spring Boot 项目中触发 Circular View Path 错误的原理与解决方案
在Spring Boot开发中,**Circular View Path**错误常因视图解析与Controller路径重名引发。当视图名称(如`login`)与请求路径相同,Spring MVC无法区分,导致无限循环调用。解决方法包括:1) 明确指定视图路径,避免重名;2) 将视图文件移至子目录;3) 确保Spring Security配置与Controller路径一致。通过合理设定视图和路径,可有效避免该问题,确保系统稳定运行。
120 0
|
3月前
|
JavaScript 前端开发 Java
Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use;端口冲突的原理与解决方案
本文解决了Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use的问题,并通过介绍端口的使用原理和操作系统的端口管理机制,可以更有效地解决端口冲突问题,并确保Web服务器能够顺利启动和运行。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
4月前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
176 12
|
5月前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
125 17
springboot自动配置原理