问题分析
在下面的代码中,我们操作了两次数据库,第一次是update更新操作,一次是insert保存数据的操作,但是如果第一步更新的操作成功了,但是第二步新增的操作却失败了,会发生什么现象呢?那接下来,我们来做一个测试 。 我们可以在代码中,人为在线索跟进的service层的方法中,构造一个错误:
@Override
public boolean followClue(ClueTrackDto clueTrackDto) {
// 先判断线索是否存在
Clue clue = clueMapper.selectById(clueTrackDto.getId());
if (clue == null) {
throw new BusinessException("当前线索不存在,无法跟进");
}
BeanUtil.copyProperties(clueTrackDto, clue, "id", "phone", "channel");
System.out.println("数据测试:" + clue);
// 修改线索跟进状态为跟进中
clue.setStatus(3);
// 更新数据到数据库
int row1 = clueMapper.updateById(clue);
// 制造异常抛出
int a = 10 /0;
// 2. 新增一条线索跟进记录
ClueTrackRecord trackRecord = new ClueTrackRecord();
trackRecord.setClueId(clue.getId());
// 从当前线程中获取当前登录用户id
Integer userId = UserHolder.getUserId();
// 设置线索跟进人id(当前登录系统的用户id)
trackRecord.setUserId(userId);
trackRecord.setSubject(clue.getSubject());
trackRecord.setLevel(clue.getLevel());
trackRecord.setRecord(clueTrackDto.getRecord());
trackRecord.setNextTime(clue.getNextTime());
trackRecord.setType(1);
int row2 = clueTrackRecordMapper.insert(trackRecord);
return row1 == 1 && row2 == 1;
}在启动项目之后,我们操作数据的时候,会发现第一步的数据成功保存到了数据库,但是第二步却没有,这是我们不允许出现的一个情况,因为这属于一个业务操作,也就是两个表中数据要么都成功,要么都失败,而不能出现有成功又失败的情况
那如何解决这个问题呢? 这需要通过数据库中的事务来解决这个问题。
事务介绍
概念: 事务是一组操作的集合,它是一个不可分割的工作单位。也就是事务可以控制一个集合中的所有子操作要么同时成功,要么同时失败
就拿线索跟进的这个业务为例,在这个业务操作中,包含了两个操作,那这两个操作是一个不可分割的工作单位。
添加事务的作用就是让这两个操作,要么同时失败,要么同时成功。
具体操作
事务控制主要三步操作:开启事务、提交事务/回滚事务
- 需要在这组操作执行之前,先开启事务
start transaction; / begin; - 所有操作如果全部都执行成功,则提交事务
commit; - 如果这组操作中,有任何一个操作执行失败,都应该回滚事务
rollback;
那接下来,我们就可以将线索跟进的业务操作,进行事务管理。 具体的SQL如下:
-- 开启事务
start transaction; / begin;
-- 1. 更新线索基本信息
update clue set subject = 1, level = 1, next_time = '2025-07-10 10:00:00', update_time = '2025-07-09 10:05:00' where id = 8;
-- 2. 添加线索的跟进记录
insert into clue_track_record (clue_id, user_id, subject, level, record, next_time, type, false_reason, create_time)
values (8, 1, 1, 1, '用户有意向,正在考虑', '2025-07-10 10:00:00', 1, null, '2025-07-09 10:05:00');
-- 提交事务(全部成功) / 回滚事务(有任何一个失败)
commit; / rollback;Spring事务
在Spring中控制事务的方式非常简单,那就是使用一个**@Transactional注解**。
它可以加在一个方法上,Spring会在方法执行开始之前来开启事务,方法执行完毕之后提交事务,如果在这个方法执行的过程当中出现了异常,就会回滚事务
这个注解可以加在业务层的方法上、类上、接口上
- 方法上:当前方法交给spring进行事务管理
- 类上:当前类中所有的方法都交由spring进行事务管理
- 接口上:接口下所有的实现类当中所有的方法都交给spring 进行事务管理
接下来,我们就可以在线索跟进业务方法上加上 @Transactional 来控制事务。
提示
@Transactional注解默认捕获RuntimeException类型的异常进行事务回滚 , 如果抛出的是非运行时异常 , 是不会进行回滚的 , 可以设置rollbackFor = Exception.class属性设置捕获所有异常回滚
@Transactional(rollbackFor = Exception.class)
@Override
public boolean followClue(ClueTrackDto clueTrackDto) {
// 先判断线索是否存在
Clue clue = clueMapper.selectById(clueTrackDto.getId());
if (clue == null) {
throw new BusinessException("当前线索不存在,无法跟进");
}
BeanUtil.copyProperties(clueTrackDto, clue, "id", "phone", "channel");
System.out.println("数据测试:" + clue);
// 修改线索跟进状态为跟进中
clue.setStatus(3);
// 更新数据到数据库
int row1 = clueMapper.updateById(clue);
// 制造异常抛出
int a = 10 /0;
// 2. 新增一条线索跟进记录
ClueTrackRecord trackRecord = new ClueTrackRecord();
trackRecord.setClueId(clue.getId());
// 从当前线程中获取当前登录用户id
Integer userId = UserHolder.getUserId();
// 设置线索跟进人id(当前登录系统的用户id)
trackRecord.setUserId(userId);
trackRecord.setSubject(clue.getSubject());
trackRecord.setLevel(clue.getLevel());
trackRecord.setRecord(clueTrackDto.getRecord());
trackRecord.setNextTime(clue.getNextTime());
trackRecord.setType(1);
int row2 = clueTrackRecordMapper.insert(trackRecord);
return row1 == 1 && row2 == 1;
}这样当我们加好事务之后,由于服务端程序引发了异常,所以事务进行回滚。如下图:

使用注意
使用事务的时候,可以加在方法上,也可以加在类上,但是一般我们只在增删改的时候增加事务,如果在整个类上加事务的话,会额外增加更多的性能消耗,因此建议加在我们需要的方法上即可。
事务日志管理
可以在application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的日志信息了,配置的代码如下:
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug