事务是逻辑上的一组操作,要么都执行,要么都不执行

1. 什么是事务

事务是逻辑上的一组操作,要么都执行,要么都不执行。

假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:

  1. 小明的余额减少 1000 元
  2. 小红的余额增加 1000 元。
public void accountMoney() {
    //小红账户多1000
		accountDao.addMoney(1000, xiaohong);
		//小明账户少1000
		accountDao.reduceMoney(1000, xiaoming);
}

如果这两个操作之间出现系统崩溃或者网络故障,导致小红余额增加而小明余额没有减少,这样会出现问题。事务就是保证这两个关键操作要么都成功,要么都要失败。

事务能否生效数据库引擎是否支持事务是关键:

数据库 引擎 支持事务
Oracle - <font color=#3CB371>是</font>
MySQL InnoDB <font color=#3CB371>是</font>
MySQL MyISAM <font color=#FF3333>否</font>

2. 事务的特性

20220403-1

3. Spring 对事务的支持

3.1 Spring 支持的两种事务管理方式

1) 编程式事务管理

通过 TransactionTemplate或者TransactionManager手动管理事务,实际应用较少使用,但是对理解 Spring 事务管理原理有帮助。

使用TransactionTemplate 进行编程式事务管理的示例代码:

@Autowired
private TransactionTemplate template;

public void testTransaction() {
    template.execute(new TransactionCallbackWithoutResult() {
      
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try {
                //业务代码...
            } catch (Exception e){
                //回滚
                status.setRollbackOnly();
            }
        }
    });
}

使用 TransactionManager 进行编程式事务管理的示例代码如下:

@Autowired
private PlatformTransactionManager manager;

public void testTransaction() {
    TransactionStatus status = manager.getTransaction(new DefaultTransactionDefinition());
    try {
        //业务代码...
        manager.commit(status);
    } catch (Exception e) {
        //回滚
        manager.rollback(status);
    }
}

2) 声明式事务管理

代码侵入性最小,原理是通过 AOP 实现,核心源码位于TransactionInterceptor中。

使用 @Transactional注解进行事务管理的示例代码如下:

@Transactional(rollbackFor = Exception.class)
public void testTransaction {
    //业务代码...
}

3.2 Spring 事务管理接口介绍

Spring 框架中,事务管理相关最重要的 3 个接口如下:

PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

3.2.1 PlatformTransactionManager:事务管理接口

Spring 不直接管理事务,而是提供多种事务管理器,事务管理器的接口是: PlatformTransactionManager

通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,具体的实现由各个平台处理。

PlatformTransactionManager 接口的具体实现如下:

20220403-2

PlatformTransactionManager接口中定义了三个方法:

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {
    //获得事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
    //提交事务
    void commit(TransactionStatus status);
    //回滚事务
    void rollback(TransactionStatus status);
}

3.2.2 TransactionDefinition:事务属性

事务属性可以理解为事务的一些基本配置,描述了事务策略如何应用到方法上,事务属性包含了 5 个方面:

20220403-3

TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {
    //传播行为
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    //隔离级别
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    // 返回事务的传播行为,默认值为 REQUIRED。
    int getPropagationBehavior();
    //返回事务的隔离级别,默认值是 DEFAULT
    int getIsolationLevel();
    // 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    int getTimeout();
    // 返回是否为只读事务,默认值为 false
    boolean isReadOnly();
    @Nullable
    String getName();
}

3.2.3 TransactionStatus:事务状态

TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。

PlatformTransactionManager.getTransaction(…)方法返回一个 TransactionStatus 对象。

TransactionStatus 接口接口内容如下:

public interface TransactionStatus {
    //是否是新的事务
    boolean isNewTransaction(); 
    //是否有恢复点
    boolean hasSavepoint();
    //设置为只回滚
    void setRollbackOnly();
    //是否为只回滚
    boolean isRollbackOnly();
    //是否已完成
    boolean isCompleted;
}

3.3 事务属性详解

3.3.1 事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

TransactionDefinition定义中包括了如下几个表示传播行为的常量:

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    ......
}

Spring 相应地定义了一个枚举类:Propagation

@Getter
@RequiredArgsConstructor
public enum Propagation {

	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

	NEVER(TransactionDefinition.PROPAGATION_NEVER),

	NESTED(TransactionDefinition.PROPAGATION_NESTED);

	private final int value;
}

事务传播行为需要注意的重点在于:

  1. <font color=#FF3333>外层事务异常时是否影响内层事务</font>

    @Service
    @RequiredArgsConstructor
    public class A {
      	private final B b;
      	private final Dao dao;
         
        //外层事务
        @Transactional(propagation = Propagation.REQUIRED)
        public void aMethod {
            dao.insert("张三");
            b.bMethod();
            //模拟外层事务异常
            throw new RuntimeException();
        }
    }
       
    @Service
    @RequiredArgsConstructor
    public class B {
      	private final Dao dao;	
         
        //内层事务
        @Transactional(propagation = Propagation.XXX)
        public void bMethod {
           dao.insert("李四");
        }
    }
    
  2. <font color=#FF3333>内层事务异常时是否影响外层事务</font>

    @Service
    @RequiredArgsConstructor
    public class A {
      	private final B b;
      	private final ADao dao;
         
        //外层事务
        @Transactional(propagation = Propagation.REQUIRED)
        public void aMethod {
            dao.insert("张三");
            b.bMethod();
        }
    }
       
    @Service
    @RequiredArgsConstructor
    public class B {
      	private final BDao dao;	
         
        //内层事务
        @Transactional(propagation = Propagation.XXX)
        public void bMethod {
           dao.insert("李四");
           //模拟内层事务异常
           throw new RuntimeException();
        }
    }
    
  3. <font color=#FF3333>内层事务异常时被外层 try-catch 后是否影响外层事务</font>

    @Service
    @RequiredArgsConstructor
    public class A {
      	private final B b;
      	private final ADao dao;
         
        //外层事务
        @Transactional(propagation = Propagation.REQUIRED)
        public void aMethod {
            dao.insert("张三");
            //外层try-catch内层事务异常
            try {
                b.bMethod();
            } catch (Exception ignored) {         
            }   
        }
    }
       
    @Service
    @RequiredArgsConstructor
    public class B {
      	private final BDao dao;	
         
        //内层事务
        @Transactional(propagation = Propagation.XXX)
        public void bMethod {
           dao.insert("李四");
           //模拟内层事务异常
           throw new RuntimeException();
        }
    }
    

正确的事务传播行为可能的值如下

1.TransactionDefinition.PROPAGATION_REQUIRED

默认使用的事务传播行为:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

2.TransactionDefinition.PROPAGATION_REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

3.TransactionDefinition.PROPAGATION_NESTED:

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED

  1. 在外部方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
  2. 如果外部方法开启事务的话,Propagation.NESTED修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。

4.TransactionDefinition.PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

内层传播行为 外层异常 内层异常 外层 try-catch 内层异常
REQUIRED 张三、李四均未入库 张三、李四均未入库 张三、李四均未入库
REQUIRES_NEW 张三未入库,李四入库 张三、李四均未入库 张三入库,李四未入库
NESTED 张三、李四均未入库 张三、李四均未入库 张三入库,李四未入库

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,使用的很少。

3.3.2 事务隔离级别

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

public interface TransactionDefinition {
    ......
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    ......
}

为了方便使用,Spring 也相应地定义了一个枚举类:Isolation

@Getter
@RequiredArgsConstructor
public enum Isolation {

	DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

	READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

	READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

	REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

	SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

	private final int value;
}

隔离级别介绍如下:

脏读、幻度、不可重复度的介绍如下:

3.3.3 事务超时属性

事务超时是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。

3.3.4 事务只读属性

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {
    ......
    // 返回是否为只读事务,默认值为 false
    boolean isReadOnly();
}

对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。

3.3.5 事务回滚规则

默认情况下,事务只有遇到RuntimeExceptionError或其子类时才会回滚,其余Exception不会回滚。

20220403-4

常用的写法如下,回滚所有Exception

@Transactional(rollbackFor= MyException.class)

可能有人会问,只声明了 Exception,那 Error 不管了吗?其实 Spring 判断是否回滚有两段逻辑:

  1. 首先判断抛出的异常是不是声明的回滚异常,是的话进行回滚
  2. 如果不满足上面的判断,再进行默认判断,是不是 RuntimeException 或 Error,是的话进行回滚

3.4 @Transactional 注解使用详解

1) @Transactional 的作用范围

  1. 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效

    源码位于AbstractFallbackTransactionAttributeSource类中,allowPublicMethodsOnly方法会被其子类AnnotationTransactionAttributeSource重写为返回true

    protected TransactionAttribute computeTransactionAttribute(
      Method method, @Nullable Class<?> targetClass) {
    		// Don't allow no-public methods as required.
    		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    			  return null;
    		}
        ......
    }
    

    个人猜想:可能是为了保证 JDK 动态代理和 CGLib 动态代理在实现上的一致性。

  2. :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。

2) @Transactional 的常用配置参数

@Transactional注解源码如下,里面包含了基本事务属性的配置:

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

	@AliasFor("transactionManager")
	String value() default "";

	@AliasFor("value")
	String transactionManager() default "";

	Propagation propagation() default Propagation.REQUIRED;

	Isolation isolation() default Isolation.DEFAULT;

	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	boolean readOnly() default false;

	Class<? extends Throwable>[] rollbackFor() default {};

	String[] rollbackForClassName() default {};

	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};
}

@Transactional 的常用配置参数总结:

属性名 说明
propagation 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过
isolation 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过
timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 指定事务是否为只读事务,默认值为 false。
rollbackFor 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。

3) @Transactional 事务注解原理

@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。

创建代理的代码如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
  .......
}

Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是TransactionInterceptor 类中的 invoke()方法。

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {  

    @Override
	  @Nullable
	  public Object invoke(MethodInvocation invocation) throws Throwable {
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); 
     ......
	}

TransactionInterceptor 类中的 invoke()方法内部实际调用的是 TransactionAspectSupport 类的 invokeWithinTransaction()方法。

这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

核心代码如下:

public class TransactionAspectSupport {
  
    @Nullable
	  protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			InvocationCallback invocation) throws Throwable {
        ......
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, identification);
        Object retVal;
		  	try {
				    retVal = invocation.proceedWithInvocation();
			  }
			  catch (Throwable ex) {
				    completeTransactionAfterThrowing(txInfo, ex);
				    throw ex;
			  }
			  finally {
				    cleanupTransactionInfo(txInfo);
			  }
        ......
    }
}

3.5 @Transactional 注解事务异常场景

3.6 常见问题

4. 鸣谢

JavaGuide