事务是否生效
1.异常被吃了
@Transactional
public void createUserWrong1(String name) {
try {
userRepository.save(new UserEntity(name));
throw new RuntimeException("error");
} catch (Exception ex) {
log.error("create user failed", ex);
}
}
解决思路:
去掉try catch
在catch中手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
2.默认rollbackFor= RuntimeException and Error
由于这里是IOException extends Exception所以不生效
@Transactional
public void createUserWrong2(String name) throws IOException {
userRepository.save(new UserEntity(name));
otherTask();
}
private void otherTask() throws IOException {
Files.readAllLines(Paths.get("file-that-not-exist"));
}
解决思路:@Transactional(rollbackFor = Exception.class)
3.this没有用到代理对象 & @Transactional的方法必须搭配public
public int createUserWrong1(String name) {
try {
this.createUserPrivate(new UserEntity(name));
} catch (Exception ex) {
log.error("create user failed because {}", ex.getMessage());
}
return userRepository.findByName(name).size();
}
@Transactional
private void createUserPrivate(UserEntity entity) {
userRepository.save(entity);
if (entity.getName().contains("test"))
throw new RuntimeException("invalid username!");
}
解决思路:
this改成自动注入自己,(spring会解决依赖循环)
private改成public
4,异常被吃了
@Transactional
public int createUserWrong3(String name) {
try {
this.createUserPublic(new UserEntity(name));
} catch (Exception ex) {
log.error("create user failed because {}", ex.getMessage());
}
return userRepository.findByName(name).size();
}
@Transactional
public void createUserPublic(UserEntity entity) {
userRepository.save(entity);
if (entity.getName().contains("test"))
throw new RuntimeException("invalid username!");
}
5.事务传播
两个事务各自独立
期待:
副逻辑subUserService.createSubUserWithExceptionWrong不影响主逻辑createMainUser的事务
事务不要互相影响
@Transactional
public void createUserWrong(UserEntity entity) {
createMainUser(entity);
subUserService.createSubUserWithExceptionWrong(entity);
}
private void createMainUser(UserEntity entity) {
userRepository.save(entity);
log.info("createMainUser finish");
}
@Service
@Slf4j
public class SubUserService {
@Transactional
public void createSubUserWithExceptionWrong(UserEntity entity) {
log.info("createSubUserWithExceptionWrong start");
userRepository.save(entity);
throw new RuntimeException("invalid status");
}
}
只收集异常也不行
期待:副逻辑不影响主逻辑的事务
@Transactional
public void createUserWrong2(UserEntity entity) {
createMainUser(entity);
try {
subUserService.createSubUserWithExceptionWrong(entity);
} catch (Exception ex) {
// 虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。
log.error("create sub user error:{}", ex.getMessage());
}
}
失败原因:虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚
@Transactional默认是Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
当前情况就是加入了之前的事务,这个事务的回滚字段设置为true,哪怕catch住了也会回滚
解决思路:
副逻辑弄一个新的事务,主逻辑catch副逻辑的异常
@Transactional
public void createUserWrong2(UserEntity entity) {
createMainUser(entity);
try {
subUserService.createSubUserWithExceptionWrong(entity);
} catch (Exception ex) {
// 虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。
log.error("create sub user error:{}", ex.getMessage());
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createSubUserWithExceptionRight(UserEntity entity) {}
事务传播分类
小册里面总结到:声明式事务注解@Transactional是写给调用者看的
每种传播机制最好在代码里面全部用一遍,就有一定的了解了,抽手打一下自己的理解
REQUIRED
SUPPORTS
MANDATORY
REQUIRES_NEW
NOT_SUPPORTED
以非事务的方式运行,无论调用者是否存在事务,自己都不受其影响
NEVER
NESTED
练手
@Transactional
public void method1(UserEntity userEntity){
userRepository.save(userEntity);
self.a(userEntity);
throw new RuntimeException("m1");
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void a(UserEntity userEntity){
UserEntity aaa = new UserEntity("NOT_SUPPORTED");
userRepository.save(aaa);
}
结果1事务不生效,a的事务生效
@Transactional
public void method2(UserEntity userEntity){
userRepository.save(userEntity);
self.b(userEntity);
}
@Transactional(propagation = Propagation.NEVER)
public void b(UserEntity userEntity){
UserEntity aaa = new UserEntity("NEVER");
userRepository.save(aaa);
}
发现调用者有事务,直接回滚报错,2也不生效
public void method3(UserEntity userEntity){
userRepository.save(userEntity);
self.b(userEntity);
throw new RuntimeException("m1");
}
@Transactional(propagation = Propagation.NEVER)
public void b(UserEntity userEntity){
UserEntity aaa = new UserEntity("NEVER");
userRepository.save(aaa);
}
发现调用者没事务,无事务执行b,然后无事务执行3
插入2条数据
哪怕b报错也插入
源码分析
总结
编程式事务,controller里面写if else
声明式事务,service里面用注解@Transactional
注意事项:
- 必须是动态代理对象调方法,不能是普通的this
- 异常不能吃掉
- 异常的类型要留意Exception
- 要根据业务判断事务是一个还是多个,关系是什么,谁成功,谁失败
- 搞不定就用编程式事务if else