Spring事务

Spring事务、事务传播机制、TransactionTemplate的使用

Spring事务三大基础

1. PlatformTransactionManager

最顶层事务接口:约束了最基础的事务方法:

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
	// 提交
    void commit(TransactionStatus status) throws TransactionException;
	// 回滚
    void rollback(TransactionStatus status) throws TransactionException;
}

不同的数据库实现此接口,来支持Spring事务;

2. TransactionDefinition

TransactionDefinition定义Spring事务属性特点:

一般用于编程式事务,相当于配置声明式事务注解中的参数,主要参数如下:

  • 事务的传播特性
  • 事务的隔离性
  • 事务的回滚规则:并不是所有异常都回滚;
  • 事务超时时间
  • 事务是否只读:对于RR隔离级别,即使是读操作,也需要避免不可重复读问题,有可能存在多次读取数据不一致现象,因此即使是读操作,也需要加上事务,而使用只读事务,可以进一步提高事务性能;
    • 如果是单个查询,不需要加事务;
    • 存在多查询,并且存在关联,需要数据一致,需要加事务;

3. TransactionStatus

可以通过TransactionStatus查看事务状态信息;

编程式事务,进行回滚、提交时需要此对象;

Spring事务的实现

声明式事务

当类或方法被

@Transactional
注解标记时,Spring通过AOP(面向切面编程)生成代理对象;

  • 代理对象拦截目标方法,在方法执行前后添加事务管理逻辑(开启事务、提交/回滚);

事务的范围边界:

  • 代理方法开始时,开启事务并关闭事务自动提交;
  • 方法执行成功,则提交事务;
  • 方法执行失败(抛出指定异常),则回滚事务;

声明式事务的回滚依赖于抛出异常:

  • 默认当方法抛出
    Error
    RuntimeException
    (下面高亮部分)时,执行回滚;如果是检查型异常则不会回滚;
Throwable
├── Error
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception
    ├── RuntimeException
    │   ├── NullPointerException
    │   ├── IndexOutOfBoundsException
    │   └── ...
    └── 检查型异常(Checked Exceptions)
        ├── IOException
        ├── SQLException
        └── ...

使用

@Transactional

  • rollbackFor
    :指定要回滚的异常;
  • propagation
    :事务传播行为,后续讨论;
  • isolation
    :事务隔离级别;
  • timeout
    :事务超时时间,具体事务的超时处理,在传播机制后再讨论;
@Transactional(  
	rollbackFor = Throwable.class,  
	propagation = Propagation.REQUIRED,  
	isolation = Isolation.READ_COMMITTED,  
	timeout = 30  
)
public void transfer(String fromUserId, String toUserId, Double amount) {
	UserAccountEntity fromUser = userAccountMapper.selectByUserId(fromUserId);  
	UserAccountEntity toUser = userAccountMapper.selectByUserId(toUserId);  
	  
	// 转账  
	Double fromUserBalance = fromUser.getBalance() - amount;  
	Double toUserBalance = toUser.getBalance() + amount;  
	userAccountMapper.updateBalance(fromUser.getUserId(), fromUserBalance);  
	userAccountMapper.updateBalance(toUser.getUserId(), toUserBalance);
}

编程式事务

手动编码指定开启事务,执行数据库操作,如果失败,手动操作事务回滚;

编码级别可以控制事务的粒度,根据一些动态数据条件,判断是否应该回滚;

  1. 使用
    PlatformTransactionManager
    手动执行事务,注意其中回滚后的其他数据库操作,此时事务已结束,其他数据库操作不会回滚;
@Resource
private PlatformTransactionManager transactionManager;

public void transfer(String fromUserId, String toUserId, Double amount) {
	// 1. 开启事务
	DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
	def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
	def.setTimeout(30);
	TransactionStatus status = transactionManager.getTransaction(def);
	try {
		// 2. 执行数据库操作
		doTransfer(fromUserId, toUserId, amount);
		// 3. 手动提交
		transactionManager.commit(status);
	} catch (Exception e) {
		// 4. 手动回滚
		transactionManager.rollback(status);
		// 5. 更新订单状态为失败,此处不会回滚,事务已经结束
		transferRecordMapper.updateStatus(recordId, "FAIL", LocalDateTime.now());
	}
}
  1. 使用
    TransactionTemplate
    执行编程式事务,比
    PlatformTransactionManager
    更方便,但缺少灵活;
    1. 可以依赖异常,当抛出Error、RuntimeException则自动回滚;
    2. 也可以手动标记回滚,方法
      execute
      方法执行完后,自动回滚;
    3. 注意
      execute
      executeWithoutResult
      中的所有操作都在事务内,都会一起提交或回滚;如代码中的高亮部分:
@Resource  
private TransactionTemplate transactionTemplate;

// 示例1:
public String transfer(String fromUserId, String toUserId, Double amount) {

	// 1. 开启事务
	transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
	transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
	transactionTemplate.setTimeout(30); // 超时时间为 30 秒
	transactionTemplate.executeWithoutResult(status -> {
		// 2. 执行数据库操作,如果抛出Error、RuntimeException则自动回滚
		doTransfer(fromUserId, toUserId, amount);
	});
}

// 示例2:
public String transfer(String fromUserId, String toUserId, Double amount) {

	// 1. 开启事务
	transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
	transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
	transactionTemplate.setTimeout(30); // 超时时间为 30 秒
	transactionTemplate.executeWithoutResult(status -> {
		try {
			// 2. 执行数据库操作
			doTransfer(fromUserId, toUserId, amount);
		} catch (Throwable e) {
			// 3. 手动标记事务回滚
			status.setRollbackOnly();
			// 4. 此处的SQL即使执行成功,最终也会被回滚
			transferRecordMapper.updateStatus(recordId, "FAIL", LocalDateTime.now());
		}
	});
}

事务的传播

事务的传播行为:不同的事务方法嵌套调用时,针对后续的事务,是共用事务、新建事务、还是不是用事务

可以用在父事务上来约束子事务,也可以用在子事务上,决定是否开启新的事务;

  1. PROPAGATION_REQUIRED(默认):共用一个事务,如果没有事务,就开启一个新的;
  2. PROPAGATION_REQUIRES_NEW:用于子事务中;
    • 子事务新建一个事务,并把父事务挂起,当前事务单独执行,执行结束不受父事务回滚影响
    • 子事务的异常也不会影响到父事务
  3. PROPAGATION_NESTED:放在父事务上,向下嵌套;
    • 父事务回滚,子事务一起回滚
    • 子事务回滚,父事务不受影响;但是子事务的异常,会影响父事务,父事务可能需要catch
  4. PROPAGATION_SUPPORTS:放在子事务上;
    • 父事务存在,就用事务
    • 不存在,就不用事务
  5. PROPAGATION_NOT_SUPPORTED:放在子事务上;
    • 无论怎么样都不使用事务,如果存在父事务,挂起父事务
    • 但是子方法抛出异常,会影响父事务
  6. PROPAGATION_MANDATORY:MANDATORY(强制性的)放在父事务,向下嵌套;
    • 存在父事务,并且是MANDATORY的,如果没有子事务,就抛出异常
  7. PROPAGATION_NEVER:用于子事务;
    • 不使用事务,如果存在父事务,就抛出异常

事务的超时回滚

  1. 超时检测:Spring通过事务管理器(
    DataSourceTransactionManager
    )在事务执行过程中周期性检查耗时。
  2. 标记回滚:一旦超时,Spring将当前事务标记为仅回滚(rollback-only)
  3. 抛出异常:抛出
    TransactionTimedOutException
  4. 回滚事务:事务拦截器捕获异常后,调用
    PlatformTransactionManager.rollback()

事务的超时与传播机制:

  1. 如果存在嵌套事务,外层事务未超时,内层事务超时,则会导致外层事务标记为回滚;
  2. 若内层事务使用
    REQUIRES_NEW
    ,此时如果子事务开启后,父事务是挂起的,子事务超时仅回滚内层独立事务不影响外层

事务失效场景

失效的本质:没有使用代理对象,或代理对象无法感知到指定异常,或数据库不支持事务;

  1. 异常被捕获,没有抛出,代理对象无法感知
@Transactional(rollbackFor = Exception.class)
public void insertWithTransaction(Order order) {
    try{
        // ...
        orderMapper.insert(order);
    } catch(Exception e){
        // ..
    }
}
  1. this
    方法调用,且注解在this方法上,主要看能不能使用代理对象执行事务方法;
    this
    无法使用代理对象来执行;
// @Transactional 加在这里事务不会失效!
public void failCondition1() {
    Order order = OrderUtils.getOrder();
    orderMapper.insert(order);
    this.insertOrderWithThisTransaction(order);

}
// 这里事务会失效,不可AOP,两条数据都会插入
@Transactional(rollbackFor = Exception.class)
public void insertOrderWithThisTransaction(Order order) {
    orderMapper.insert(order);
    throw new RuntimeException();
}
  1. private
    final
    static
    修饰的方法,这些方法都无法实现代理;
  2. 数据库不支持事务,Spring事务本质上还是底层数据库的事务,数据库引擎需要支持事务;

如何处理大事务

首先是:避免长事务(耗时过久的事务);

  • 长时间占用数据库连接;
  • 事务涉及的数据库资源范围大,数据被锁,阻塞其他线程;
  • 如果回滚了,也会需要很长时间;

如何避免事务时间过长:

  1. 避免事务内耗时任务:RPC、大计算任务;
  2. 要执行的事务并发过高,事务执行时锁竞争严重;
  3. 减少声明式事务,使用编程式事务,在需要事务的时候才开启事务;