Administrator
发布于 2022-12-15 / 32 阅读
0
0

Spring注解驱动---声明式事务

与之前的编程式事务不同,声明式事务不用再配置xml文件,只要几个注解就能实现事务。

声明式事务实现

1、导包

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.17</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>

2、写配置

@Configuration配置类

//开启事务管理功能
@EnableTransactionManagement
//告诉Spring这是个配置类
@Configuration
//包扫描
@ComponentScan("com.lly.Tx")
public class TxConfig {


    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/tx?serverTimezone=Asia/Shanghai");
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
        return new JdbcTemplate(dataSource());
    }

    //注册事务管理器在容器中
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);//要加入数据源,控制数据源
    }

}

业务逻辑:

@Service
public class TxServiceImpl {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insert(){
        String sql ="INSERT INTO account(username,balance) VALUES(?,?)";
        String username = UUID.randomUUID().toString().substring(0, 5);
        jdbcTemplate.update(sql,username,2000);

    }
}

控制器:

@Controller
public class TxController {

    @Autowired
    private TxServiceImpl txServiceImpl;

    @Transactional //在需要执行事务的方法上加上这个注解,替代xml方法中的事务通知配置和切入点表达式
    public void Txcontroller(){
        txServiceImpl.insert();
        System.out.println("插入数据完成……");
        int i = 10/0;
    }
}

3、测试

附上@Transactional失效的场景:

1、应用在非public修饰的方法上
2、注解属性 propagation 设置错误
3、注解属性 rollbackFor 设置错误
4、同一个类中方法调用
5、数据库引擎不支持事务

事务出现异常回滚这里可以深入一下,但不是现在

事务实现原理

首先要知道的是:事务管理通过AOP机制实现

AOP原理:【看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?】

1、@EnableTransactionManagement

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//给容器中导入TransactionManagementConfigurationSelector组件
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

	boolean proxyTargetClass() default false;

//AdviceMode的默认值是PROXY
	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;

}

2、TransactionManagementConfigurationSelector

这个Selector中有个selectImports方法,能根据AdviceMode不同的值往容器中导入不同的组件,这里我没有做任何别的配置,所以AdviceMode还是默认值PROXY。

  @Override
  protected String[] selectImports(AdviceMode adviceMode) {
      switch (adviceMode) {
          case PROXY:
          //所以,会往容器里导入AutoProxyRegistrar、ProxyTransactionManagementConfiguration
              return new String[] {AutoProxyRegistrar.class.getName(),
                      ProxyTransactionManagementConfiguration.class.getName()};
          case ASPECTJ:
              return new String[] {determineTransactionAspectClass()};
          default:
              return null;
      }
  }

2.1 AutoProxyRegistrar

在这个类里

if (mode == AdviceMode.PROXY) {
//会注册一个AutoProxyCreator,step into ;之后注册这个组件的流程和之前的
//AOP流程就一模一样了。
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                if ((Boolean) proxyTargetClass) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                    return;
                }

2.2 ProxyTransactionManagementConfiguration

ProxyTransactionManagementConfiguration是一个配置类,负责导入三个组件:

1、事务增强器(BeanFactoryTransactionAttributeSourceAdvisor)
2、事务注解@Transactional的解析器(AnnotationTransactionAttributeSource)
3、事务方法拦截器(TransactionInterceptor);保存了事务的属性信息,事务管理器


事务增强器(Advisor,其实也是AOP中说到的Advisor),在事务类创建的时候,被AutoProxyRegistrar导入的组件
internalAutoProxyCreator拦截,拦截的业务逻辑就是增强事务类的事务方法(AOP中拦截的就是通知方法),	
而BeanFactoryTransactionAttributeSourceAdvisor作为增强器,与需要增强的方法(这里指被@Transactional标记的方法)进行匹配,
匹配成功的增强器,最后转成拦截器(MethodInterceptor,就是TransactionInterceptor),
然后与目标方法一起在拦截器链中被执行,达到方法增强的效果;

目标方法执行

执行流程和AOP的是一样的

new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
||
依次执行拦截器链;索引相等时执行目标方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    ……
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
||
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
||
1、在目标方法执行之前,会先获取事务相关的属性
TransactionAttributeSource tas = getTransactionAttributeSource();

2、再获取PlatformTransactionManager事务管理器;
   如果事先没有添加指定任何TransactionManager
   最终会从容器中按照类型获取一个PlatformTransactionManager
   
   这里为什么会没有,是因为ProxyTransactionManagementConfiguration给我们配置的
   AnnotationTransactionAttributeSource不会指明使用哪个TransactionManager,虽然我们自己在
   配置类中配置了PlatformTransactionManager,但容器只是将PlatformTransactionManager注册了。
   并不知道就是要用的TransactionManager。所以会在要使用的时候按照类型到容器里找。如果没有配置的话
   就会抛nosuchbean异常。

retVal = invocation.proceedWithInvocation();


最后,目标方法执行会有异常和正常两种情况,又根据不同的传播行为和配置,事务又会有回滚和提交的操作

try {
//执行方法
    retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//出现异常会处理事务异常
    // target invocation exception
    completeTransactionAfterThrowing(txInfo, ex);
    throw ex;
}

completeTransactionAfterThrowing会根据配置,决定对事务进行提交或者是回滚。
想深入了解的话可以结合传播行为进行debug。


贴一张事务异常的处理机制(只是部分得)

image-1671176232164

如果抛出了这个异常,那么事务是会回滚了的;

org.springframework.transaction.UnexpectedRollbackException: 
Transaction rolled back because it has been marked as rollback-only

在抛这个异常的上面有注释:
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.

意思是本来在全局上被标记了是要回滚的,但是一直拿不到异常信息,就会抛出这个异常。
在抛出这个异常之前会执行rollback操作

总结

通过@EnableTransactionManagement注解会为容器中导入
AutoProxyRegistrar、ProxyTransactionManagementConfiguration两个组件;

AutoProxyRegistrar会给容器再注册InfrastructureAdvisorAutoProxyCreator组件,利用后置处理器在对象创建以后,将包装对象,并返回一个代理对象(增强器),返回的代理对象执行方法利用拦截器链调用;

ProxyTransactionManagementConfiguration会给容器中注册事务增强器,并给其获取解析事务注解的信息;同时也用事务拦截器先获取事务相关属性,再获取PlatformTransactionManager事务管理器,再执行目标方法,如果异常就按配置执行回滚或提交操作,正常则利用事务管理器提交事务。

后来有一点的想法

在AOP里,有一个操作是在创建bean之前会尝试从缓存中拿,有时候可能会很迷惑这样的操作。因为在容器加载的时候去拿那些bean肯定是没有的,为什么还要做这样的步骤。

直到事务这里,在注册配置类的时候,会将扫描增强器,但这时候被扫描到的增强器还没有注册的(因为for循环获取beanname还没轮到它们),这时候就要提前创建,然后就会加入到缓存中,之后循环走到这个bean的时候,那么缓存中就会有这个bean了。所以就无需重复创建。

可以理解为组件依赖。一个组件依赖另一个组件,但另一个组件还没创建呢,这时候也会走这样的流程。


评论