浅谈 @Transactional 事务注解原理

@Transactional 是什么?

描述单个方法或类的事务属性。
在类级别声明此注解时,它默认应用于声明类及其子类的所有方法。请注意,它不适用于类层次结构中的祖先类;继承的方法需要在本地重新声明才能参与子类级别的注释。

这里我们看一下,@Transactional 注解类源码,具体如下:

package org.springframework.transaction.annotation;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	// transactionManager的别名。
	@AliasFor("transactionManager")
	String value() default "";
	
	// 指定事务的限定符值。
	// 可用于确定目标事务管理器,匹配特定TransactionManager bean 定义的限定符值(或 bean 名称)。
	@AliasFor("value")
	String transactionManager() default "";
	
	// 定义零 (0) 个或多个事务标签。标签可用于描述事务,并且可以由各个事务管理器进行评估。标签可能仅用于描述目的或映射到预定义的事务管理器特定选项。
	String[] label() default {};
	
	// 事务传播类型。
	Propagation propagation() default Propagation.REQUIRED;

	// 事务隔离级别。默认为Isolation.DEFAULT 。
	Isolation isolation() default Isolation.DEFAULT;

	// 此事务的超时时间(以秒为单位)。默认为底层事务系统的默认超时
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	// 此事务的超时时间(以秒为单位)。默认为底层事务系统的默认超时。
	String timeoutString() default "";

	// 如果事务实际上是只读的,则可以设置为true的布尔标志,允许在运行时进行相应的优化。默认为false 。
	boolean readOnly() default false;

	// 定义零 (0) 个或多个异常classes ,它们必须是Throwable的子类,指示哪些异常类型必须导致事务回滚。
	Class<? extends Throwable>[] rollbackFor() default {};

	// 定义零 (0) 个或多个异常名称(对于必须是Throwable子类的异常),指示哪些异常类型必须导致事务回滚。
	String[] rollbackForClassName() default {};

	// 定义零 (0) 个或多个异常Classes ,它们必须是Throwable的子类,指示哪些异常类型不得导致事务回滚。
	Class<? extends Throwable>[] noRollbackFor() default {};
	
	// 定义零 (0) 个或多个异常名称(对于必须是Throwable子类的异常),指示哪些异常类型不得导致事务回滚。
	String[] noRollbackForClassName() default {};

事务隔离级别

Isolation枚举类,定义了四种事务隔离级别,如下图:

  • DEFAULT(默认):使用底层数据存储的默认隔离级别。所有其他级别对应于 JDBC 隔离级别。
  • READ_UNCOMMITTED(读取未提交):指示可能发生脏读、不可重复读和幻读的常量。此级别允许在提交该行中的任何更改之前由另一个事务读取由一个事务更改的行(“脏读”)。如果回滚任何更改,则第二个事务将检索到无效行。
  • READ_COMMITTED(读取已提交):指示防止脏读的常量;可能发生不可重复读取和幻读。此级别仅禁止事务读取其中包含未提交更改的行。
  • REPEATABLE_READ(可重复读):指示防止脏读和不可重复读的常量;可能发生幻读。该级别禁止一个事务读取其中未提交更改的行,同时也禁止一个事务读取一行,第二个事务更改该行,第一个事务重新读取该行,第二次得到不同值的情况( “不可重复读取”)。
  • SERIALIZABLE(可串行化):指示防止脏读、不可重复读和幻读的常量。此级别包括ISOLATION_REPEATABLE_READ中的禁止,并进一步禁止以下情况:一个事务读取所有满足WHERE条件的行,第二个事务插入满足WHERE条件的行,第一个事务重新读取相同的条件,检索附加的“幻”行在第二次读取。

image.png

@Transactional 核心原理

@Transactional 实现原理是基于 Spring AOP 实现的。

涉及的类

  • BeanFactoryTransactionAttributeSourceAdvisor:由TransactionAttributeSource驱动的顾问,用于为事务性方法包含事务建议 bean
  • DynamicAdvisedInterceptor:通用 AOP 回调。当目标是动态的或代理未冻结时使用。
  • TransactionInterceptor:AOP 拦截器 MethodInterceptor 用于声明式事务管理。

大概流程

image.png

怎么使用 @Transactional ?

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

  1. 在启动类上添加@EnableTransactionManagement注解。
  2. @Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean save(SystemInfo systemInfo) {
        R<Boolean> result = this.handlerSave(systemInfo);

        if (super.save(systemInfo)) {
            return result.getDefExec() ? systemAlarmRuleService.insertDefault(systemInfo.getId()) : result.getData();
        }

        return true;
    }

扩展

@Transactional 失效的情况

  1. @Transactional 修饰的方法为非 public 方法。
  2. 在 @Transactional 方法内部捕获了异常,没有在 catch 代码块中重新抛出异常,需要手动回滚,TransactionAspectSupport.currentTransactionStatus.setRollbackOnly()
  3. 抛出非运行时异常。需要指定@Transactional(rollbackFor = Exception.class)
  4. 同一个类中,内部方法直接调用。类内部没有添加 @Transactional 的方法时,调用了 @Transactional 的方法
  5. 新开启一个线程。Spring实现事务是通过ThreadLocal绑定当前数据库连接到当前线程的,新开启即获取的连接不是同一个。
  6. 数据库本身不支持。例如:MySQL没有使用InnoDB引擎。
  7. 事务传播属性设置错误。