前言
在Spring中,事务的实现方式分为两种:编程式事务和声明式事务,
编程式事务:
编程式事务是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。
声明式事务:
声明式事务建立在AOP之上,原理是对方法进行拦截,在目标方法执行之前添加事务,目标方法执行后根据执行情况进行事务的提交或回滚。@Transactional注解是实现声明式事务的方式之一,它能保证方法内多个数据库操作要么同时成功、要么同时失败。
因为编程式事务实现相对麻烦且是侵入式的,而声明式事务是非侵入性的且实现极其简单,因此我们都提倡使用声明式事务。
本文主要简单的分享一下@Transactional相关的知识。
一、@Transactional概述
1、@Transactional的作用范围
@Transactional注解作用在接口、类、类方法上。
- 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
- 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
- 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
2、@Transactional的属性
属性 | 说明 |
name | 配置文件中有多个TransactionManager时,通过name属性指定使用哪个事务管理器 |
propagation | 事务的传播行为,默认为Propagation.REQUIRED |
isolation | 事务的隔离级别,默认为Isolation.DEFAULT,使用数据库的默认隔离级别 |
timeout | 事务的超时时间,默认为-1,如果事务在该时间内没有完成则自动回滚 |
readOnly | 是否是只读事务 |
rollbackFor | 触发事务回滚的异常类型,存在多个时用逗号分隔 |
noRollbackFor | 不触发事务回滚的异常类型 |
二、@Transactional的实效场景
1.非 public 修饰的方法
@Transactional是基于动态代理的,Spring的代理工厂在启动时会扫描所有的类和方法,并检查方法的修饰符是否为public,非public时不会获取@Transactional的属性信息,这时@Transactional的动态代理对象为空。
/**
* 新增数据
*
* @param user 实体
* @return 新增结果
*/
@PostMapping("add")
@Transactional
ResponseEntity<String> add(@RequestBody User user) {
return ResponseEntity.ok(this.userService.insert(user) > 0 ? "success" : "fail");
}
/**
* 新增数据
*
* @param user 实例对象
* @return 实例对象
*/
@Override
public int insert(User user) {
int insert = this.userMapper.insert(user);
//定义一个异常
return insert/0;
}
报异常后仍然添加成功,事务没有回滚
2.同一个类中,非@Transactional方法调用类内部 @Transactional 方法
由于动态代理的原因,类内部方法的调用是通过this调用的,不会使用动态代理对象,事务不会回滚。
/**
* 新增数据
*
* @param user 实体
* @return 新增结果
*/
@PostMapping("add")
@Transactional
ResponseEntity<String> add(@RequestBody User user) {
return ResponseEntity.ok(this.userService.insert(user) > 0 ? "success" : "fail");
}
/**
* 新增数据
*
* @param user 实例对象
* @return 实例对象
*/
@Override
public int insert(User user) {
return test(user);
}
@Transactional
public int test(User user) {
int insert = this.userMapper.insert(user);
//定义一个异常
return insert/0;
}
报异常后仍然添加成功,事务没有回滚
3.异常被try/catch了导致@Transactional失效
@Transactional是根据抛出的异常来回滚的,如果异常被捕获了没有抛出的话,事务就不会回滚。
/**
* 新增数据
*
* @param user 实体
* @return 新增结果
*/
@PostMapping("add")
@Transactional
ResponseEntity<String> add(@RequestBody User user) {
return ResponseEntity.ok(this.userService.insert(user) > 0 ? "success" : "fail");
}
/**
* 新增数据
*
* @param user 实例对象
* @return 实例对象
*/
@Transactional
@Override
public int insert(User user) {
int insert = 0;
try {
insert =this.userMapper.insert(user)/0;
//定义一个异常
} catch (Exception e) {
e.printStackTrace();
}
return insert;
}
报异常后仍然添加成功,事务没有回滚
4.rollbackFor属性设置不对
@Transactional默认情况下,仅对RuntimeException和Error进行回滚。如果不是的它们及它们的子孙异常的话,就不会回滚。
所以,在自定义异常的时候,要做好适当的规划,如果要影响事务回滚,可以定义为RuntimeException的子类;如果不是RuntimeException,但也希望触发回滚,那么可以使用rollbackFor属性来指定要回滚的异常。
/**
* 新增数据
*
* @param user 实体
* @return 新增结果
*/
@PostMapping("add")
ResponseEntity<String> add(@RequestBody User user) throws Exception {
this.userService.insert1(user);
return ResponseEntity.ok(null);
}
/**
* 新增数据
*
* @param user 实例对象
* @return 实例对象
*/
@Transactional
@Override
public void insert1(User user) throws Exception {
this.userMapper.insert(user);
//定义一个异常
throw new Exception("测试事务");
}
报异常后仍然添加成功,事务没有回滚
因为代码中指定的是Exception异常,而非RuntimeException异常,所以事务没有进行回滚,如果是Exception异常的话,需要在注解参数上加rollbackFor = Exception.class
才能进行事务回滚。
/**
* 新增数据
*
* @param user 实例对象
* @return 实例对象
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void insert1(User user) throws Exception {
this.userMapper.insert(user);
//定义一个异常
throw new Exception("测试事务");
}
测试结果
报异常后发现数据库里数据没有添加,事务回滚成功。
5.数据库引擎不支持事务
@Transactional 是给调用的数据库发送了:开始事务、提交事务、回滚事务的指令,但是如果数据库本身不支持事务,比如 MySQL 中设置了使用 MyISAM 引擎,即使在程序中添加了 @Transactional 注解,那么依然不会有事务的行为,因为MyISAM 引擎本身是不支持事务的。
三、总结
使用@Transactional注解在开发时确实很方便,但是我们也得注意它的实效场景。