概述
Spring中, Java方法的事务传播类型通过 @Transactional 注解进行指明, 并通过该注解的 propagation 属性指明事务传播的具体类型.@Transactional 注解的使用非常灵活, 可以注解在服务接口上, 也可以注解在服务类的方法上, 还可以注解在Spring Repository的接口方法上.
@Transactional(propagation = Propagation.REQUIRED)
各种传播类型的说明
Spring 中一共有七中事务传播类型. 分别说明如下:
一. Propagation.REQUIRED
如果当前已经存在事务, 那么加入该事务, 如果不存在事务, 创建一个事务, 然后执行事务操作. 这是默认的传播属性值. 也是最常见的选择.
何谓当前?
当前也就是你所声明的服务方法被调用的时候.
何谓已经存在事务?
也就是说在声明的事务方法被调用的时候, 就已经在一个事务当中了. 比如下面的伪代码:
@Transactional
public void service(){
serviceA();
serviceB();
}
@Transactional
serviceA();
@Transactional
serviceB();
service() 方法开启了一个事务, 当 serviceA(); serviceB(); 被调用的额时候回加入 service() 所在的事务上下文.
注意: @Transactional 没有指明 propagation 属性, 取默认值 Propagation.REQUIRED
含义为: 要求的, 必须的. 如果被注解的方法有这个传播属性, 它的行为是:
- 如果它作为一个子事务方法, 在其他事务方法中被调用, 那么该方法不会创建新的事务, 使用现有的父级别的事务.
- 如果它作为一个子事务方法, 没有在其他事务方法中被调用, 而是在非事务方法中直接调用, 那么它会创建一个新的事务来执行数据库操作.
归纳为: 有就用, 没有就创建(事务).
也就可以理解 Propagation.REQUIRED 的意思了 -- 要求的. 不管怎样它能够保证操作总是在一个事务中进行的.
应用场景: 不知道方法的调用者是否创建了事务, 但是要求当前被调用的方法必须在一个事务当中执行.
Propagation.MANDATORY
支持当前事务,如果当前没有事务,就抛出异常
对于这个类型, 它一般作为一个事务的一部分定义. 不能独立执行. 一般作为一个事务中的子操作. 比如经典的转账作为例子.
两个账户 A 和 B 之间转账, 从 A 转 1000 到 B , 拆分为两个操作, 并且要在一个事务中执行.
操作1: 先把 1000 加到 B 账户上, 如果 B 账户增加成功, 执行操作2
操作2: 如果 B 账户增加成功, 那么 A 账户扣除 1000.
当两个操作同时成功时, 事务执行成功, 否则, 任意一步错误, 回滚之前的全部操作. 那么对应的操作1和操作2我们可以实现为两个 Propagation.MANDATORY
类型的Java方法. 并且把这两个方法放在一个 Propagation.REQUIRED
类型的方法中, 例如:
// 服务接口
interface AccountService {
TransactionLog transfer(Long accountA, Long accountB, BigDecimal amount);
}
// 服务实现
@Service
class AccountServiceImpl implements AccountService {
// 父事务, 用于组织多个子事务.
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public TransactionLog transfer(Long accountA, Long accountB, BigDecimal amount) {
// 先加后减
addAmount(accountB, amount)
addAmount(accountA, amount.negate())
}
}
// 数据库访问对象
@Repository
class interface AccountRepository extends JpaRepository {
// 余额操作(自增, 自减)
@Modifying
@Transactional(propagation = Propagation.MANDATORY)
@Query(value = "UPDATE account a SET a.balance = a.balance + ?2 WHERE a.id = ?1")
void addAmount(Long id, BigInteger amount);
}
Propagation.REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起
特征: 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 提交 或 回滚 而不依赖于外部事务,它拥有自己的隔离范围, 自己的锁, 等等.当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. Propagation.REQUIRES_NEW 常用于日志记录,或者交易失败仍需要留痕.还有就是 时序控制, 支付依赖于已经创建的订单, 无订单不支付. 先要有订单才能支付. 常见于事务步骤 要求时序 的情况.
开启一个全新的事务, 一般用于分布式事务中的一个前期步骤. 比如一键购功能. 主要步骤包括:
-
创建订单
- 创建订单
- 扣除库存
- 创建物流订单
-
发起支付
- 对于支付,清算相关的业务流程可以参考: 深度解析:什么是支付核心?
下面的伪代码模拟了一个外层事务和内层事务的业务过程.
// 一键购服务
class BuyService {
@Transactional(propagation = Propagation.REQUIRED)
public void buyDirectly() {
Order OrderService.createOrder(OrderDto orderDto);
PayService.pay(PayDto payDto);
}
}
interface OrderService {
void createOrder(OrderDto orderDto);
}
interface PayService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
Payment pay(PayDto payDto);
}
OrderService.createOrder 运行在外层事务中, 如果创建订单失败, 就没必要发起支付了, 直接回滚了, 根本就到不了支付这一步.
当绑定的银行卡余额不足的情况, PayService.pay(); 是可以回滚的, 而不会影响 buyDirectly 整个事务, OrderService.createOrder(); 成功. 向银行卡不足金额后, 可以重新发起支付, 完成购买过程.
注意: Propagation.REQUIRES_NEW 如果作为一个子事务运行, 调用者和被调这不要在同一个服务类中(因为Spring AOP动态代理的限制, 在同一个类中事务是不起作用的)Propagation.REQUIRES_NEW 的一般使用场景是作为内层事务可以单独回滚. 而不是回滚整个外层事务. 因此如果调用者和被调用者如果在一个类中, Propagation.REQUIRES_NEW 注解的方法并 不会 开启一个新的事务. 因此就达不到内层事务单独回滚的目的.
归纳: 内层事务可以独立回滚, 不影响外层事务.
前提是外层事务的方法不能和内层事务的方法在同一个服务类中
Propagation.NOT_SUPPORTED
以非事务方式执行操作, 如果当前存在事务, 就把当前事务挂起
Propagation.NEVER
如果当前存在事务, 则抛出异常, 否则在无事务环境上执行代码
对于方法中只存在只读操作, 我们可以使用这个. 一般事务注解为:
@Transactional(propagation = Propagation.NEVER, readOnly = true)
Propagation.NESTED
嵌套事务, 它的作用相当于 Propagation.REQUIRED 和 Propagation.REQUIRES_NEW 的合体
特征: Propagation.NESTED 启动一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个存储点(Savepoint). 如果这个嵌套事务失败, 我们将回滚到此 存储点. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. 由此可见, Propagation.REQUIRES_NEW 和 Propagation.NESTED 的最大区别在于, Propagation.REQUIRES_NEW 完全是一个新的事务, 而 Propagation.NESTED 则是外部事务的子事务, 如果外部事务提交, 嵌套事务也会被,这个规则同样适用于回滚.
注意: 使用此种事务传播类型, 需要设置事务管理器的 nestedTransactionAllowed 属性为 true
/** * TransactionConfig.java * * 事务配置 */ @Configuration @EnableTransactionManagement public class Transaction { @Bean public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory); transactionManager.setNestedTransactionAllowed(true); return transactionManager; } }
Propagation.SUPPORTS
表示当前方法不必需要具有一个事务上下文, 但是如果有一个事务的话, 它也可以在这个事务中运行
归纳: 有没有父级事务(外层事务)都可以接受
这个类型一般用于不会修改(UPDATE, DELETE)数据库状态的Java方法里面. 如果说在上述 AccountServiceImpl 实现类的 transfer 方法中还要通过调用其他的方法(包含SELECT语句)去返回某些数据,那么该方法可以标注为 Propagation.SUPPORTS 类型.
总结
关键字: 创建, 挂起, 恢复, 非事务执行, 事务执行.
@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.