spring的事务

在这里插入图片描述

1、手动回滚事务

数据平台二期项目时修改一条任务时要同时调用删除原定时任务和添加新定时任务的方法,这俩方法返回的都是布尔值,即使在修改方法上加上了@Transactional注解,这俩方法如果发生了非RuntimeException异常或者操作失败返回false,也不会回滚前边修改的数据库。所以,需要添加手动回滚代码。
手动回滚spring事务的方法
1、
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
如果返回为false则添加上面代码。
2、假如调用的定时任务的方法没有捕获异常,则用try catch包住该方法,在catch里throw new RuntimeEcxeption
3、设置回滚点

//设置回滚点,
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();  

//回滚到savePoint事务点
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);

4、修改注解的参数
改变默认规则:
1 让checked异常也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)

2 让运行时异常不回滚: @Transactional(notRollbackFor=RunTimeException.class)

3 不需要事务管理的(只查询的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
注意: 如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛
try{}catch{throw Exception}

2、事务失效问题

事务失效的原因:
数据库不支持事务
@Transactional标注的方法不是public的
自调用导致

@Service
public class DmzService {
 
 public void saveAB(A a, B b) {
  saveA(a);
  saveB(b);
 }
 
 @Transactional
 public void saveA(A a) {
  dao.saveA(a);
 }
 
 @Transactional
 public void saveB(B b){
  dao.saveB(a);
 }
}

以上代码就是自调用,spring生成了代理类,但因为saveAB方法没有标注事务注解其实调用的是目标类即原来类里的savaA和savaB方法
下面也是自调用

@Service
public class DmzService {
 @Transactional
 public void save(A a, B b) {
  saveB(b);
 }
 
 @Transactional(propagation = Propagation.REQUIRES_NEW)
 public void saveB(B b){
  dao.saveB(a);
 }
}

预期是这样
在这里插入图片描述
其实是这样
在这里插入图片描述
由于saveB方法实际上是由dmzService也就是目标类自己调用的,所以在saveB方法的前后并不会执行事务的相关操作。这也是自调用带来问题的根本原因:「自调用时,调用的是目标类中的方法而不是代理类中的方法」
解决方法:
1.自己注入自己,然后显示的调用

public class DmzService {// 自己注入自己
@Autowired
DmzService dmzService;
@Transactional
public void save(A a, B b) {
dmzService.saveB(b);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveB(B b){
dao.saveB(a);
}
}

2.利用AopContext,如下:@Service
public class DmzService {
@Transactional
public void save(A a, B b) {
((DmzService) AopContext.currentProxy()).saveB(b);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveB(B b){
dao.saveB(a);
}
}
❝使用上面这种解决方案需要注意的是,需要在配置类上新增一个配置// exposeProxy=true代表将代理类放入到线程上下文中,默认是false
@EnableAspectJAutoProxy(exposeProxy = true)

4、内外部事务问题

@Service
public class DmzService {
 
 @Autowired
 IndexService indexService;
 
 @Transactional
 public void testRollbackOnly() {
  try {
   indexService.a();
  } catch (ClassNotFoundException e) {
   System.out.println("catch");
  }
 }
}
 
@Service
public class IndexService {
 @Transactional(rollbackFor = Exception.class)
 public void a() throws ClassNotFoundException{
  // ......
  throw new ClassNotFoundException();
 }
}

在上面这个例子中,DmzService的testRollbackOnly方法跟IndexService的a方法都开启了事务,并且事务的传播级别为required,所以当我们在testRollbackOnly中调用IndexService的a方法时这两个方法应当是共用的一个事务。按照这种思路,虽然IndexService的a方法抛出了异常,但是我们在testRollbackOnly将异常捕获了,那么这个事务应该是可以正常提交的,为什么会抛出异常呢?
总结起来,「主要的原因就是因为内部事务回滚时将整个大事务做了一个rollbackOnly的标记」,所以即使我们在外部事务中catch了抛出的异常,整个事务仍然无法正常提交,并且如果你希望正常提交,Spring还会抛出一个异常。

「解决方案」:

这个解决方案要依赖业务而定,你要明确你想要的结果是什么

1.内部事务发生异常,外部事务catch异常后,内部事务自行回滚,不影响外部事务

将内部事务的传播级别设置为nested/requires_new均可。在我们的例子中就是做如下修改: 
// @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void a() throws ClassNotFoundException{ // ......
throw new ClassNotFoundException();
}

虽然这两者都能得到上面的结果,但是它们之间还是有不同的。当传播级别为requires_new时,两个事务完全没有联系,各自都有自己的事务管理机制(开启事务、关闭事务、回滚事务)。但是传播级别为nested时,实际上只存在一个事务,只是在调用a方法时设置了一个保存点,当a方法回滚时,实际上是回滚到保存点上,并且当外部事务提交时,内部事务才会提交,外部事务如果回滚,内部事务会跟着回滚。

2.内部事务发生异常时,外部事务catch异常后,内外两个事务都回滚,但是方法不抛出异常

@Transactional
public void testRollbackOnly() {
try {
indexService.a();
} catch (ClassNotFoundException e) { 
// 加上这句代码
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
}
}

显式的回滚是有预期的,不会抛出异常

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值