前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >@Transactional作用(成像原理)

@Transactional作用(成像原理)

作者头像
全栈程序员站长
发布2022-07-29 15:54:56
8830
发布2022-07-29 15:54:56
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

目录

1、@Transactional的使用

1.1、实践

2、@Transactional原理分析

2.1、初始化阶段

2.1.1、@Import和[@ImportSelector + @ImportBeanDefinitionRegistor ]

2.1.2、InfrastructureAdvisorAutoProxyCreator组件

2.1.3、ProxyTransactionManagementConfiguration组件

2.2、调用执行阶段

3、事务失效原因分析

4、小结

前言

事务主要保证了数据操作的原子性,一致性,隔离性和持久性。 事务不会跨线程传播,事务不能跨数据源。

1、@Transactional的使用

  1. 导入相关依赖,数据源、数据库驱动、Spring-jdbc模块;
  2. 事务操作加@Transactional;
  3. 打开事务管理功能,对datasource进行控制;
  4. 注册事务管理器;
  5. 配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据;

1.1、实践

使用注解@Transactional

代码语言:javascript
复制
    //第一步:导入相关数据库依赖;
    //第二步:加事务注解;
    @Transactional(rollbackFor = {Exception.class})
    public void updateAccount(int id) {
        int rows = accounMapper.deduction(id);
        if (rows > 0) {
            System.out.println("秒杀库存修改成功");
            insertGoodOrder();
        } else {
            System.out.println("秒杀修改失败");
        }
    }

开启事务管理器并注册事务管理器

其实我们之前在xml配置里, 会配置开启基于注解的事务管理功能,和AOP一样@EnableAspectJAutoProxy,@EnableTransactionalManagement开启基于注解的事务管理功能;

代码语言:javascript
复制
@Configuration
@ComponentScan("com.king.db")
@EnableTransactionManagement //第三步:开启事务管理功能,让@Transactional生效
public class DataSourceConfig {


    //创建数据源 这个c3p0封装了JDBC, dataSource 接口的实现
    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("kongyin");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql//localhost:3306/order");
        return dataSource;
    }

    @Bean //第四步:注册事务管理器bean
    public PlatformTransactionManager platformTransactionManager() throws PropertyVetoException {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean //第五步:jdbcTemplate能简化增查改删的操作
    public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
        return new JdbcTemplate(dataSource());
    }
}

或者在spring的xml中配置:

代码语言:javascript
复制
<!--事务管理器配置,market中对双数据源的配置-->
<tx:annotation-driven transaction-manager="transactionManager"/>

//注册事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    <qualifier value="settleTransactionManager"/>
</bean>

可以看到@Transactional的使用非常简单,那么它是如何实现的呢?下面来看一下它的实现原理。

2、@Transactional原理分析

  • 思考一: 为什么在方法上加了@Transactional就有了事务能力?
  • 思考二: 为什么有时候加了@Transactional却不起作用?

先通过一段伪代码来解释一下注解事务的原理,例如一个方法加入@Transactional注解后,一个方法执行的伪代码执行如下,最终还是对数据库连接的控制使用。

代码语言:javascript
复制
@Transactional
public void doInvokeWithTransactional(){
    doBusiness()
}

如果在上述方法上添加一个@transactional后的等价操作:
public void doInvokeWithTransactional(){
    transactionalManager.beginTranscation; //事务管理器-开启事务-拿到一个事务
    autoCommit=false;     //关闭事务自动提交,需要手动提交
    public void invoke(){
        try{
            doBusiness(); //执行业务逻辑处理
        }cache(Exception e){
            Rollback();   //回滚
        }
        commit();         //提交
    }
}

@Transactional的实现我把它大致分为两个阶段:

  • 第一阶段:目标方法增强的初始化阶段;
  • 第二阶段:目标方法的执行阶段;

2.1、初始化阶段

当加了@Transactional注解后,需要做一系列的初始化工作,例如要使某个方法操作具有事务能力就的对该方法在容器启动的时候对其增强处理,赋予它事务的能力。为了了解其原理,我们从源码的开启事务管理功能入口开始: @ EnableTransactionManagement

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class) //利用Import给容器添加一个Selector组件;
public @interface EnableTransactionManagement {

    //使用JDK或者是Cglib动态代理
   boolean proxyTargetClass() default false;

    //默认事务增强器是什么模式:代理
   AdviceMode mode() default AdviceMode.PROXY;

   //最低的优先级
   int order() default Ordered.LOWEST_PRECEDENCE;

}

2.1.1、@Import和[@ImportSelector + @ImportBeanDefinitionRegistor ]

可以看到EnableTransactionManagement利用@Import给容器添加了一个组件 TransactionManagementConfigurationSelector

注意:spring的源码中经常会出现 @Import和[ @ImportSelector + @ImportBeanDefinitionRegistor ] 的组合来为容器动态批量添加组件的套路实现。

  • ImportSelector是一个接口,只需要实现selectImport()方法,返回的是一个数组,即可给容器批量的注册Bean实例;
  • ImportBeanDefinitionRegistor也是一个接口,只需要实现registorBeanDefinition()方法就可以实现给容器添加bean实例;
代码语言:javascript
复制
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
   @Override
   protected String[] selectImports(AdviceMode adviceMode) {
     switch (adviceMode) {
       case PROXY:
         //利用ImportSelector开始往容器中注册2个组件;
         return new String[] {
                            AutoProxyRegistrar.class.getName(),                       //注册的第一个组件;
                            ProxyTransactionManagementConfiguration.class.getName()}; //注册的第二个组件
       case ASPECTJ:
            return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
       default:
            return null;
      }
   }
}

//AdviceModeImportSelector继承了ImportSelector,说明具有了向容器注册组件的能力;
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {

   /**
    * The default advice mode attribute name.
    */
   public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";

   protected String[] selectImports(AdviceMode adviceMode) {}
}

从上面可以看出,主要是利用@Import(TransactionManagementConfigurationSelector.class) 给容器导入两个重要的组件:

* AutoProxyRegistrar – 创建事务动态代理创建器,给容器创建一个动态代理创建器:InfrastructureAdvisorAutoProxyCreator;

* ProxyTransactionManagementConfiguration– 获取事务管理器生成事务拦截器,解析保存事务注解(传播属性-回滚方式)等等;

下面主要看一看这两个组件做了些什么?

2.1.2、InfrastructureAdvisorAutoProxyCreator组件

第一个组件: InfrastructureAdvisorAutoProxyCreator 事务动态代理创建器

AutoProxyRegistar利用ImporyBeanDefinitionRegistrar给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件。

代码语言:javascript
复制
//通过ImportBeanDefinitionRegistrar给容器中添加组件:InfrastructureAdvisorAutoProxyCreator
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

   private final Log logger = LogFactory.getLog(getClass());

   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      boolean candidateFound = false;
      Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
      for (String annoType : annoTypes) {

         if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
               Boolean.class == proxyTargetClass.getClass()) {
            candidateFound = true;
            if (mode == AdviceMode.PROXY) {//看它给容器中注册了什么组件
               AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
               if ((Boolean) proxyTargetClass) {
                  AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                  return;
               }
            }
}

@Nullable //给容器中添加InfrastructureAdvisorAutoProxyCreator组件
public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,Object source) {
        //这里开始给容器注册:InfrastructureAdvisorAutoProxyCreator 事务动态代理创建器组件
   return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

那么InfrastructureAdvisorAutoProxyCreator主要做了什么呢?

通过源码的不断跟踪,你会发现 InfrastructureAdvisorAutoProxyCreator其实就是实现了spring的后置处理器postProcessor接口,用来 创建增强的实例bean,也就是让doInvokeWithTransaction()方法具备事务的能力。 利用后置处理器机制在对象创建以后,包装对象Bean,返回 一个增强的代理对象,之后通过拦截链来完成调用。

下面主要是显示 InfrastructureAdvisorAutoProxyCreator这个类的继承关系,最终实现了BeanPostProcessor接口,就是一个后置处理器,用来对我们的业务bean实现增强。

代码语言:javascript
复制
public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
    public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
        public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
                      implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {}

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
    public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
        @Nullable  //Bean实例前置增强
        default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
           return bean;
        }

        @Nullable //Bean实例后置增强
        default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
           return bean;
        }
}

2.1.3、ProxyTransactionManagementConfiguration组件

第二个组件:ProxyTransactionManagementConfiguration 事务管理器的配置

ProxyTransactionManagementConfiguration 做了什么?

(1)给容器中注册生成的事务增强器Bean;

* AnnotationTransactionAttributeSource 作用:解析事务注解元信息,传播属性,超时时间,隔离级别

(2)生成事务拦截器:

* TransactionInterceptor,保存了事务属性信息,事务管理器;它是一个MethodInterceptor;

代码语言:javascript
复制
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

   //开始事务的元数据属性解析
   @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   //对属性元信息的一些增强,比如在注解中设置的一些参数:传播属性propagation,回滚的条件rollbackFor等等
   //@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
   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//主要用于保存事务属性的信息,封装成一个TransactionInterceptor
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public TransactionInterceptor transactionInterceptor() {
      TransactionInterceptor interceptor = new TransactionInterceptor();
      interceptor.setTransactionAttributeSource(transactionAttributeSource());
      if (this.txManager != null) {
         interceptor.setTransactionManager(this.txManager);
      }
      return interceptor;
   }
}


//开始解析事务的属性值,会发现很多事务属性在这里都有:propagation/isolation/timeout等等
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
   RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
    //事务传播属性的设置
   Propagation propagation = attributes.getEnum("propagation");
   rbta.setPropagationBehavior(propagation.value());
    //事务的隔离属性的设置
   Isolation isolation = attributes.getEnum("isolation");
   rbta.setIsolationLevel(isolation.value());
    //事务的超时时间设置
   rbta.setTimeout(attributes.getNumber("timeout").intValue());
   rbta.setReadOnly(attributes.getBoolean("readOnly"));
   rbta.setQualifier(attributes.getString("value"));
   ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<>();
    //事务的回滚条件设置
   Class<?>[] rbf = attributes.getClassArray("rollbackFor");
   for (Class<?> rbRule : rbf) {
      RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
      rollBackRules.add(rule);
   }
    //设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚
   String[] rbfc = attributes.getStringArray("rollbackForClassName");
   for (String rbRule : rbfc) {
      RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
      rollBackRules.add(rule);
   }
   Class<?>[] nrbf = attributes.getClassArray("noRollbackFor");
   for (Class<?> rbRule : nrbf) {
      NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
      rollBackRules.add(rule);
   }
    //设置不回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,事务不回滚
   String[] nrbfc = attributes.getStringArray("noRollbackForClassName");
   for (String rbRule : nrbfc) {
      NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
      rollBackRules.add(rule);
   }
   rbta.getRollbackRules().addAll(rollBackRules);
   return rbta;
}

到这里:第一就阶段的初始化任务就完成了,核心任务:

利用TransactionManagementConfigurationSelector给容器中导入两个组件:

(1)InfrastructureAdvisorAutoProxyCreator

AutoProxyRegistrar给容器中注册一个 InfrastructureAdvisorAutoProxyCreator组件,它其实就是一个后置处理器,一个动态代理创建器,利用后置处理器和动态代理对目标方法进行增强,返回一个增强的实例对象,代理对象执行方法利用拦截器链进行调用;

(2)ProxyTransactionManagementConfiguration

对事务管理器的获取,对事务的元信息进行处理,对目标方法本身的执行,主要是事务能力细节的代理实现,然后给容器中注册配置生成的事务增强器Bean;

2.2、调用执行阶段

目标方法的调用,开始调用TransactionInterceptor.invoke() 方法,这个是事务执行的核心,思路流程代码写的很清晰:

代码语言:javascript
复制
//这里拦截后封装成 MethodInterceptor,保存了事务的信息,和aop的逻辑一样
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {


@Override
@Nullable //动态代理的调用
public Object invoke(final 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);
}

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
      final InvocationCallback invocation) throws Throwable {

   // If the transaction attribute is null, the method is non-transactional.
    //(1):获取(实践第二步中)设置的事务属性信息(propagation = Propagation.REQUIRED, rollbackFor = Exception.class),直接从内存中加载;
   TransactionAttributeSource tas = getTransactionAttributeSource();
   final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    //(2):获取(实践第四步中)注册的事务管理器-PlatformTransactionManager,加载到容器中;
   final PlatformTransactionManager tm = determineTransactionManager(txAttr);
   final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

   if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
      // Standard transaction demarcation with getTransaction and commit/rollback calls.
        //(3):得到事务管理器,关闭事务自动提交;
      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
      Object retVal = null;
      try {
         // This is an around advice: Invoke the next interceptor in the chain.
         // This will normally result in a target object being invoked.
        //(4): 开始执行目标方法本身doBusiness();
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
         // target invocation exception
        //(4.1): 如果执行过程中抛出异常则回滚
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }
        //(4.2) 如果执行成功,则提交事务;
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }


// (4.1) :回滚事务
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
   if (txInfo != null && txInfo.getTransactionStatus() != null) {
      if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
         try {
            txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
         }
      }

Spring拿到数据库连接池的连接connection后,事务管理器关闭自动提交,通过方法执行的结果的成功和失败判断该事务是提交还是会滚。至此,我们的声明式事务@Transactional就完成了对方法的事务增强,使其具备了事务的能力。

3、事务失效原因分析

对于思考二问题: 为什么有时候加了@Transactional却不起作用?

现在就会很清楚了,从设计思想来看,@Transaction的设计是基于AOP的,所以首先就是具有事务能力的Bean实例一定是通过动态代理增强后的实例对象,也就是标签事务必须被spring代理增强,否则事务将失效,也就是类似本地调用,this的调用事务将会失效。

例如:

代码语言:javascript
复制
public class TransactionalTest{
    @Transactional
    public void doInvokeWithTransactional(){
        doBusiness();
        enjoy(); //不好意,这里等价于:this.enjoy(),注意,本地调用是不会走动态代理的,不会对enjoy()方法进行增强;所以这里enjoy()的事务会失效;
    }

    @Transactional
    public void enjoy(){
        doEnjoy();
    }

其他的事务失效的注意点:

原因一:入口的方法必须是public,如果是protected和private方法,则事务不起作用, final 方法 和 static 方法不能添加事务,加了也不生效

一些在private方法上面加@Transactional,这件事有两重意思:

  • 1、你的方法是private的话,即使加上@Transactional注解,该注解也无效,不会开启事务,发生异常时不会回滚。
  • 2、即使你的方法是public的,但是如果被private的方法调用,@Transactional注解同样也会失效。

原因:出于安全考虑,因为是私有的方法,不能被访问增强,不应该用事务切入,这是合理的。

原因二 Spring的事务管理默认只对出 现非受检运行期异常(java.lang.RuntimeException及其子类)进行回滚, Exception:受检异常 – Checked异常 事务@transaction 不回滚

原因三:请确保你的业务和事务入口在同一个线程里, 事务不能垮线程传播,否则事务也是不生效的,比如下面代码事务不生效:

代码语言:javascript
复制
@Transactional
@Override
public void save(Order orderInfo ) {
    new Thread(() -> {
          addOrder(orderInfo); //事务失效,不会垮线程传播;
          System.out.println(1 / 0);
    }).start();
}

4、小结

注解事务设计思想:spring的声明式事务都是基于AOP的,其实 所有加了注解的方法都是利用spring的后置处理器,使用对应的处理类对当前的作用域的方法或者类做一个拦截增强处理,返回一个增强的代理类,实现注解的增强功能。

第一阶段:初始化阶段

  • 创建事务增强的后置处理器,主要用来对目标的方法和类进行增强;;
  • 使用动态代理,创建具有事务能力的增强代理类;

第二阶段:执行调用阶段

  • 2.1 通过AOP机制,调用动态增强代理对象的目标方法: CglibAoProxy. intercept();
  • 2.2 获取目标方法事务 拦截器链:也即设置的通知的方法 List<Object> chain = this.advised.getInterceptors()利用拦截器的链式机制,依次进入每一个拦截器通知进行执行;压栈的存储通知方法,再出栈调用通知方法;
  • 2.3 执行事务拦截器: TransactionalInterceptor实现了methodInterceptor:调用invokeWithTransaction()函数

(1)获取事务的属性信息: AnnotationTransactionAttributeSource

(2)获取事务管理器: PlatfromTransactionMavager

(3)关闭事务的自动提交

(4)执行目标方法: interceptor(). invoke()

(4.1)执行异常:回滚;

(4.2)执行正常:提交;

这里也就回答了开篇思考一的问题了。

水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/129274.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年4月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、@Transactional的使用
    • 1.1、实践
    • 2、@Transactional原理分析
      • 2.1、初始化阶段
        • 2.1.1、@Import和[@ImportSelector + @ImportBeanDefinitionRegistor ]
        • 2.1.2、InfrastructureAdvisorAutoProxyCreator组件
        • 2.1.3、ProxyTransactionManagementConfiguration组件
      • 2.2、调用执行阶段
      • 3、事务失效原因分析
      • 4、小结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档