Spring框架是日常开发中必定会用到的框架,而它的事务机制,也是面试的重中之重,今天我们来讨论一下Spring的事务机制。
什么是事务
厨房没有酱油了,妈妈叫你去商店买一瓶回来。买酱油的过程,你给分成三个步骤:
1.去妈妈那拿钱
2.去商店挑好酱油
3.把钱给收银员
这3个步骤,只要其中一个步骤完不成,买酱油失败。只有全部完成,买酱油成功。这种一组操作,要么全部成功,要么全部不成功,就叫做事务。
什么是Spring事务
Spring支持声明式事务和编程式事务两种方式。
1.声明式事务
在SpringBoot中可以在方法上添加@Transanal注解来实现
// 开启一个事务,并指定回滚异常类型为Exception。
@Transactional(rollbackFor = Exception.class)
public void addUser(User user) throws Exception {
// 对数据库进行操作。
userDao.addUser(user);
}
@Transanal注解参数分为以下几种方式:
REQUIRED:如果没有事务就新建一个事务,否则就加入已有事务,是默认的事务机制
REQUIRES_NEW:不管有没有事务,都新建一个事务。例子事务A下面有个事务B,事务A的回滚不会影响事务B,导致事务B回滚
NESTED:如果外层没有事务就新建一个事务,如果外层有事务就嵌入其他事务,事务A下面有事务B。如果事务A回滚,事务B也会回滚,但是事务B回滚不会影响事务A。
SUPPORTS:如果当前存在一个事务,则加入到该事务中;如果不存在事务,则不使用事务。这种传播行为通常用于测试场景。
MANDATORY:必须在一个事务中执行此操作,如果当前没有事务,则抛出异常。
NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则挂起该事务。
2.编程式事务
声明式事务非常简单,方法上加一个注解就能搞定,但是它的范围太大了。如果你只是想要对方法中的某一段代码做事务处理,不想上升到整个方法,那么可以采用编程式事务。
@Autowired
private PlatformTransactionManager transactionManager;
public void createProduct() {
//普通操作A
.....
//通过TransactionTemplateh类来获取平台事务管理器。
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
// 通过execute方法来启动一个事务。
transactionTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
try {
// 事务操作B
........
} catch (Exception e) {
// 事务操作B发生异常,进行回滚
status.setRollbackOnly();
throw e;
}
return null;
}
});
//操作C
........
}
Spring事务的陷阱
@Transactional(rollbackFor = Exception.class)
public boolean addFollowRecord() {
//插入一条记录
CustomerFollowUpRecord insertRecord = new CustomerFollowUpRecord();
insertRecord.setFsUserId("fs_123456");
customerMapperExt.insert(record);
ThreadPoolUtil.execute(() -> {
// 查找记录
CustomerFollowUpRecord record=customerMapperExt.getByFsUserId(insertRecord);
//打印日志
if (Objects.nonNull(record)){
log.info("异步操作打印XXXXXXX");
}
});
//其他操作
doOtherThing();
}
上面是一个Spring事务方法,先插入一条数据,然后异步从数据库查询出来,打印日志。但是实际场景跑的时候,你会发现,日志很有可能不会打印。
这是为什么呢?
因为@Transactional注解的方法,是要把方法里面的所有代码执行完毕,才会提交到数据库。也就是说实际的执行顺序可能是这样:
1.CustomerFollowUpRecordrecord=customerMapperExt.getByFsUserId(insertRecord);
2.customerMapperExt.insert(record);
这也是之前有个同事写的代码,看不出问题,然后我帮忙看了下,发现的。所以要注意Spring事务的陷阱,代码的书写顺序,不代表执行顺序。