目录
1. 什么是事务?
2. Spring 事务三大基础设施
2.1 PlatformTransactionManager 平台事务管理器
2.2 TransactionDefinition 事务属性定义
2.3 TransactionStatus 事务状态
3. Transaction 注解
4. Spring 事务角色
5. @Transaction 注解属性
5.1 事务的回滚规则
5.2 事务的传播行为
了解过 MySQL 事务的同学应该都知道,事务通常是由多个 SQL 语句共同构成的一组逻辑操作单元,要么同成功,要么同失败。而我们的 Spring 事务也是一样的道理,它可以保证数据层或业务层的一系列操作要么同时成功,要么同时失败。其底层只是对 MySQL 的事务做了封装,因此如果你明白数据库的事务,了解 Spring 事务也是轻而易举。
在 Spring 框架中,它给我们提供了三个和事务有关的最基础也最重要的三个接口或类。它们是 PlatformTransactionManager,TransactionDefinition,TransactionStatus。下面我来逐个说一下它们的作用。
平台事务管理器 PlatformTransactionManager 是一个接口,里面定义了三个最基本的方法,它从上层规范了子类的行为,具体的代码实现交给了各个不同的平台。
这一点类似于我们之前所学习过的 JDBC ,sun 公司制定了一套数据库接口标准,各大数据库厂商去实现这些接口的方法,我们程序员不需要关心各个数据库厂商的具体实现。我们所熟知使用的 DatasourceTransactionManager 就是该接口的其中一个实现类。
它的源码我截取出来如下:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 提交事务方法
void commit(TransactionStatus var1) throws TransactionException;
// 回滚事务方法
void rollback(TransactionStatus var1) throws TransactionException;
}
这个接口中定义了很多的属性,它更多是用来描述事务的具体规则,主要是 事务的隔离性,传播性,回滚规则,超时时间,事务是否只读。
我截取了该接口的源码如下
getIsolationLevel() 获取事务的隔离级别,如数据库的读未提交,读已提交,可重复读,序列化四种隔离级别;
getPropagationBehavior() 获取事务的传播行为,后面我会专门说到它,也是面试中会问到的点,这里先混个眼熟;
getTimeout() 获取事务的超时时间;
isReadOnly() 是否为只读;
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
// 判断是否有保存点,类似于我们数据库中的事务保存点,回滚事务可以让其回滚至该点
boolean hasSavepoint();
// 该方法可以把底层的方法刷新到数据库中去,现在用的不太多了
void flush();
}
Spring 为我们提供了注解的方式开启事务,传统的编程式事务由于事务代码与业务代码糅合在一起,导致项目耦合度过高,因此在 Spring Boot 项目中,我们通常可以利用注解开启事务。
而且,@Transactions 注解不仅可以加在类上,也可以加在接口上,@Transaction 注解加载业务层实现类上,表示开启当前方法的事务,为了降低耦合度,我建议各位同学在业务层接口对各个接口也做 @Transactions 事务标记,标记当前接口中的所有方法是事务。
如果标注在接口上,表示该接口中的所有抽象方法都是事务,如下所示;
@Transactional
public interface UserService {
User queryById(Long id);
boolean transMoney();
}
如果标注在类上,表示该类中所有的方法都是事务,如下接口实现类;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return userMapper.findById(id);
}
@Override
public boolean transMoney() {
userMapper.outMoney();
userMapper.inMoney();
return true;
}
}
如果标注在方法上,表示当前方法是一个事务,如下代码;
@Override
@Transactional
public boolean transMoney() {
userMapper.outMoney();
userMapper.inMoney();
return true;
}
Spring 事务中的角色分为事务管理员与事务协调员。
如下图
我们现在定义了一个两个接口转账接口。原本,我们的 outMoney 和 inMoney 分别是两个事务,互不相关。
然后定义一个转账业务方法,调用两个转账接口。我们在业务方法上加上@Transaction注解,表示开启 Spring 事务,这又是一个新的事务。
一旦我们开启了 Spring 事务,那么原本的 outMoney 和 inMoney 这两个事务就会加入到我的Spring 事务中,这样一来,三个事务就变成了一个事务,当我们的业务层中 tansfer 方法出现了错误,它就会回滚。就可以做到所有的操作同成功同失败的效果。
事务管理员:发起事务方,在 Spring 中通常指代业务层开启事务的方法。
事务协调员:加入事务方,在 Spring 中通常指代数据层方法,也可以是业务方法。
我们在使用 @Transaction 这个注解的时候,可以为其配置很多属性
这里我们需要重点掌握的是 rollbackFor(回滚规则) 和 propagation(传播行为) 。
我们通常所熟知的,在事务中,一旦发生异常,就要进行回滚。但是在 Spring 事务中,有一些异常它是不会回滚的,所以就需要我们手动设置。
在 Spring 事务中,只有遇到 Error 错误和 RuntimeException 运行时异常才会发生回滚,其余的异常即便发生事务也不会进行回滚。
例如在事务中如果发生了 IOException,它就不会回滚事务。如下图所示,
我们在两个转账操作之间加上 IOException,执行该方法,那么之际情况就是 outMoney 方法正常执行不会滚,inMoney 方法因为抛出异常不会运行。
想要解决此问题,我们就需要在 @Transaction 注解中配置 rollbackFor 属性,指定碰到哪些异常时回滚,如下图所示,我们直接将属性配置在接口上,降低耦合度
配置完成之后,此时该事务再遇到 IOException,就会回滚事务。
事务传播行为,我们也可以把它理解为 事务协调者对事务管理者的态度,什么意思呢?
刚才我说到,Spring 开启事务之后,所有的事务都会加入到Spring 开启的这个事务,使所有事务成为一个事务达到同成功同失败的目的。
我来举个例子,刚才的转账案例,我加入一个功能,不管转账是否成功,都需要在数据库中进行记录,说白了就是添加一个功能。
做法:正常情况下,我们需要建立数据库转账记录表——>Java中创建转账日志实体类——>编写数据访问层接口——>编写业务方法。然后将该业务方法也作为事务加入到 转账事务中就可以了。
但各位仔细看,不管转账是否成功,都需要在数据库进行记录,那么现在就出现问题了,如果我们按照原来的 Spring 事务操作是有问题的。如果转账事务成功,数据库日志会记录。如果转账事务不成功,事务回滚,数据库的日志表中还会有记录吗?日志是肯定也不会存在数据库中,如此一来,就无法达到我们的目的。
此时,就需要用到事务的传播行为这一属性,它需要在 @Transaction 这一注解中进行属性配置的。还是和刚才一样,我们在方法的接口上配置@Transaction(propagation = REQUIRES_NEW)。
所有传播行为如下表所示
可以看到,REQUIRES_NEW 中事务协调员在事务管理员有事务的情况下,它又单独开辟了一个事务,这样一来,我们的转账日志记录功能就与转账功能本身分割开了,成了两个事务,那么转账日志记录这个事务就不会随着转账事务的回滚而回滚,不管是否成功,都会进行记录,这样就得到了我们的目的。