一、什么是事务
数据库事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么一起成功,要么一起失败,是一个不可分割的工作单元。
二、Spring Boot中的事务管理
三大基础组件:PlatformTransactionManager事务管理器接口、TransactionDefinition、TransactionStatus
- PlatformTransactionManager : 事务管理器
- TransactionDefinition : 事务的一些基础属性信息,如超时时间、隔离级别、传播属性、回滚规则等。
- TransactionStatus: 事务的一些状态信息,如是否是一个新的事务、是否已被标记为回滚。
事务的传播性:在不同service层调事务。
在 Spring Boot 中操作事务有两种方式:编程式事务或声明式事务。
1、编程式事务
把事务代码嵌入到业务逻辑中。代码耦合度高。
在 Spring Boot 中实现编程式事务有两种方法:对于编程式事务管理,spring推荐使用TransactionTemplate。
1、使用 TransactionTemplate 对象实现编程式事务;
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private FileDao fileDao;
@Test
public void test() {
// 1、定义事务的默认属性
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// 2、获取TransactionStatus
TransactionStatus status = transactionManager.getTransaction(definition);
try {
FilePo filePo = new FilePo();
filePo.setId(UUID.randomUUID().toString());
fileDao.insert(filePo);
// 提交事务
transactionManager.commit(status);
} catch (DataAccessException e) {
// 回滚事务
transactionManager.rollback(status);
}
}
2、使用更加底层的TransactionManager对象实现编程式事务。
2、声明式事务
原理:基于AOP的思想,将事务和业务代码解耦,无入侵。
一般通过在方法上或类上添加 @Transactional 注解实现声明式事务。当标注在类上时,表示该类的所有public
方法都将支持事务。当标注在方法上时,表示该方法将在一个事务内执行。
在方法执行前,自动开启事务;在方法成功执行完,自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。
1、@Transactional事务的属性:
参数 意义
isolation 事务隔离级别,默认为DEFAULT
propagation 事务传播机制,默认为REQUIRED
readOnly 事务读写性,默认为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
noRollbackFor 一组异常类,遇到时不回滚,默认为{}
noRollbackForClassName 一组异常类名,遇到时不回滚,默认为{}
rollbackFor 一组异常类,遇到时回滚,默认为{}
rollbackForClassName 一组异常类名,遇到时回滚,默认为{}
timeout 超时时间,以秒为单位,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
value 可选的限定描述符,指定使用的事务管理器,默认为“”
2、什么时候需要添加@Transactional注解:
3、@Transactional失效场景:
1、同一个类中方法调用,导致失效:由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
EmployeeServiceImpl类:
@Transactional
public void calledFunc() {
LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
employeeMapper.update(null, wrapper);
throw new RuntimeException("异常");
}
@Override
public void test() {
calledFunc();
}
Test类:
@Test
void testTransaction() {
employeeService.test();
}
此时虽然方法抛出了异常,但是事务控制失效,异常事务并没有回滚,数据更新成功了。
必须通过代理过的类从外部调用目标方法才能生效。
2、如果一个方法添加了@Transactional注解声明事务,而方法内又使用了try catch 捕捉异常,则方法内的异常捕捉会覆盖事务对异常的判断,从而异致事务失效而不回滚。
EmployeeServiceImpl类:
@Transactional
public void test2() {
try {
LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
employeeMapper.update(null, wrapper);
throw new RuntimeException("异常");
} catch (RuntimeException e) {
System.out.println("捕获到了异常");
}
}
如何解决:
第一个方法:给@Transactional注解增加:rollbackFor属性并手动抛出指定的异常。
第二个方法:在捕捉到异常后手动rollback。
3、@Transactional 属性 rollbackFor 设置错误,导致异常不满足回滚条件
EmployeeServiceImpl类:
@Transactional
public void calledFunc() throws Exception{
LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
employeeMapper.update(null, wrapper);
throw new Exception("受检异常");
}
@Override
@Transactional
public void test() throws Exception {
calledFunc();
}
@Test
void testTransaction() {
try {
employeeService.test();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
解决方法:设置rollbackFor:@Transactional(rollbackFor = Exception.class)
4、@Transactional 注解传播属性 propagation 设置错误,父事务回滚子事务不会回滚:
EmployeeServiceImpl类:
@Override
@Transactional
public void test() {
LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "2");
employeeMapper.update(null, wrapper);
detectApplicationDealer.calledFunc();
throw new RuntimeException("受检异常");
}
NameDetectApplicationDealer类:
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void calledFunc() {
LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
employeeMapper.update(null, wrapper);
}
调用方法抛出异常,被调用方法使用Propagation.REQUIRES_NEW属性,会开启一个新的事务,外层事务不影响内部事务的提交/回滚;内部事务出现异常,会影响外部事务的提交/回滚。
5、@Transactional 应用在非 public 修饰的方法上。
在同一个类里,被调用的方法写@Transactional注解没有意义,因为它是通过this来调的而不是代理对象。
三、开发中遇到的问题分享
1、异常没注明属性。
2、事务未提交导致查询数据库查不到。
一、rollbackFor属性
如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。
@Transactional(rollbackFor = Exception.class)
二、一个带@Transactional注解的方法调用其他方法,被调用的方法需要加@Transactional注解吗?
这要视情况而定。
如果你希望被调用的方法在其自己的事务中运行(即,如果该方法失败,只有该方法的结果会回滚,而不会影响到调用它的方法的事务),那么你应该在被调用的方法上添加@Transactional注解。
然而,如果你希望被调用的方法和调用它的方法共享同一个事务(即,如果被调用的方法失败,整个事务都会回滚),那么你不需要在被调用的方法上添加@Transactional注解1。在这种情况下,即使被调用的方法没有@Transactional注解,它仍然会在调用它的方法的事务上下文中运行。
三、什么时候需要对一个方法添加@Transactional注解
1、一个方法中的所有操作都需要在同一个事务中完成,那么这个方法就应该被注解为@Transactional;
2、涉及到多个数据库操作,这些操作需要在同一个事务中完成以确保数据的一致性;
3、调用其他方法的方法:如果一个方法调用了其他方法,并且这些方法需要在同一个事务中运行,那么调用方法应该被注解为@Transactional。这样,无论被调用的方法是否有@Transactional注解,它们都会在同一个事务上下文中运行;
4、需要自己的事务的方法:如果一个方法需要在其自己的事务中运行,那么这个方法应该被注解为@Transactional。这样,如果这个方法失败,只有这个方法的结果会回滚,而不会影响到调用它的方法的事务。
四、一个方法对数据库只有一次查询和一次插入操作,需要添加@Transactional注解吗?
如果一个方法只包含一次查询和一次插入操作,那么是否需要添加@Transactional注解取决于你的具体需求。
如果查询和插入操作是相互独立的,也就是说,插入操作的成功与否不依赖于查询操作的结果,那么你可能不需要使用@Transactional注解。在这种情况下,即使插入操作失败,查询操作也不会被回滚。
然而,如果查询和插入操作是相关联的,也就是说,插入操作的成功与否依赖于查询操作的结果,那么你应该使用@Transactional注解。这样,如果插入操作失败,那么整个事务(包括查询操作)都会被回滚,以保持数据的一致性。
总的来说,是否需要对一个方法添加@Transactional注解取决于你的业务逻辑和这个方法的需求。你应该根据你的具体情况来决定是否使用@Transactional注解。
在一个被调用的类里,如果入口处的方法没加事务,在它里面被它调用的方法加事务,这个事务是不会生效的!因为没有切面。
一个类调用另一个类的方法,被调用的方法是否需要再单独加事务呢?
五、SpringBoot事务不回滚的可能情况
面试突击86:SpringBoot 事务不回滚?怎么解决?_Java_王磊_InfoQ写作社区
Mysql隔离级别
事务的传播机制
六、参考:
详解Spring的事务管理PlatformTransactionManager-腾讯云开发者社区-腾讯云 (tencent.com)
Spring Boot中的事务是如何实现的 - 知乎 (zhihu.com)