@Transcation失效原因
@Transcation失效原因
前言
最近入职新公司了,毕竟才毕业半年,做的都是一点简单任务,但是测试时候发现,某个接口调用异常居然事务没有回滚?我表示非常诧异,明明已经添加@EnableTransactionManagement注解(事务管理器),并且相应的service也添加了@Transcation,居然还是失效,所以开始探究原因。
认识一下@Transcation
@Transcation本身是spring容器一个注解,配置这个注解能够实现声明式事务。
其实大致上原理就是通过一个aop进行一个切面事务的管理,配合上threadlocal保存连接来进行事务的管理。这里我也不具体讲解原理了,有兴趣可以看看别人的博客。
统计一下常见的事务失效情况
1.注解抛出异常错误
@Transactional(rollbackFor = RuntimeException.class)
public Result<Book> saveBook(Book book) throws Exception {
bookMapper.insert(book);
if (!book.getName().equals("老人与海")){
throw new Exception();
}else {
return ResultUtil.success(book);
}
}
Transactional 只捕获RuntimeException和Error异常和子类异常,但是实际代码抛出的是Exception,所以事务不会回滚
解决方法
@Transactional(rollbackFor = Exception.class)
2.事务的传播行为设置错误
@Transactional(propagation = Propagation.NEVER)
public Result<Book> saveBook(Book book) throws Exception {
bookMapper.insert(book);
if (!book.getName().equals("老人与海")){
throw new Exception();
}else {
return ResultUtil.success(book);
}
}
propagation传播行为设置了事务不生效,NOT_SUPPORTED ,NEVER,MANDATORY 3种都会让事务不生效
3.同类调用同类其他方法
public Result<Book> saveBook1(Book book) {
this.saveBook2(book);
return ResultUtil.success(book);
}
@Transactional
public void saveBook2(Book book){
bookMapper.insert(book);
int a = 1/0;
}
saveBook1 普通方法去调用有事务的方法,saveBook2是不会回滚的,以为他本身类没经过springAop代理,所以会回滚失败。
解决方法
他没经过代理,我们第一个时间想到的就是通过spring容器去获取它的带来
public Result<Book> saveBook1(Book book) {
((BookServiceImpl)(AopContext.currentProxy())).saveBook2(book);
return ResultUtil.success(book);
}
然后会发现报错
java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
然后我们可以在配置上加入@EnableAspectJAutoProxy(exposeProxy = true)这个注解,主要通过threadlocal保存用户当前的代理类,然后就可以完成了
@Transactional
public Result<Book> saveBook1(Book book) {
bookMapper.insert(book);
this.saveBook2(book);
return ResultUtil.success(book);
}
@Transactional
public void saveBook2(Book book){
bookMapper.insert(book);
int a = 1/0;
}
如果saveBook1方法上有事务调用,去调用saveBook2方法是可以实现事务的;
总结:
如果方法A不带事务,方法B/C带有事务,事务是不会生效的
如果方法A带事务,方法B/C带有事务,事务是会生效的
4.内部消化异常
上面提到是通过aop去捕获特定异常,从而进行事务回滚,如果自己内部消化了会怎么样?
@Transactional
public Result<Book> saveBook1(Book book) {
try {
bookMapper.insert(book);
int a = 1/0;
}catch (Exception e){
e.printStackTrace();
}
return ResultUtil.success(book);
}
答案是失效的,其实你也可以一步步跟进事务的源码,其实他也是捕获异常来进行决定是否事务回滚,如果你内部已经消化了,人家就捕捉不到了,进而肯定失效了。
5.方法不是public
暂时还不知道,有待探究
关于自己碰到的问题
先上代码(因为是公司内部代码,就自己随便模拟一下)
@Transactional
public Result<Book> saveBook1(Book book) {
bookMapper.insert(book);
int a = 1/0;
book.setName("老人与海");
bookMapper.insert(book);
return ResultUtil.success(book);
}
中间a=1/0 模拟一个我代码写错的原因
但是答案是事务不会滚,所以我就跟着异常走了一波
首先是
MethodProxy类中的invoke方法然后
CglibAopProxy类中的proceed()方法然后
就是下面一个自定义的aop切面方法????
这个aop主要是统计下接口时间记录日志收集的
@Aspect
@Component
@Slf4j
public class LogAspect {
public final static long time_out = 3000L;
// 配置织入点
@Pointcut("execution(* com.example.demo.service.*.*(..))")
public void logPointCut()
{
}
@Around("logPointCut()")
protected Result around(ProceedingJoinPoint point) throws Throwable
{
// 获取类名
String className = point.getTarget().getClass().getSimpleName();
// 获取方法名
String methodName = point.getSignature().getName();
try {
StopWatch sw = new StopWatch("test");
sw.start();
Result proceed = (Result)point.proceed();
sw.stop();
long totalTimeMillis = sw.getTotalTimeMillis();
if (time_out>totalTimeMillis){
log.warn("{}类名的{}方法接口超时",className,methodName);
}
return proceed;
}catch (Exception e){
log.error("{}类名的{}方法接口超时",className,methodName);
return ResultUtil.error(999,"接口超时");
}
}
}
然后我算是明白了,他先去走这个aop的方法,然后直接返回了???
其实这个时候大家都猜到了,多个aop之间如何进行一个先后顺序,我来测一个例子吧
2021-04-26 20:48:00.989 INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect1 : AOP1前置
2021-04-26 20:48:00.989 INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect2 : AOP2前置
2021-04-26 20:48:03.172 INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect2 : AOP2后置
2021-04-26 20:48:03.172 INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect2 : AOP2 finally块后置
2021-04-26 20:48:03.172 INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect1 : AOP1后置
2021-04-26 20:48:03.172 INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect1 : AOP1 finally块后置
没什么好说了 就是 类似一个同心圆
那么怎么保证AOP执行先后顺序呢,如果在没加的前提下,我猜测可能是根据名字来进行先后(有待考证),如何想加的话就要通过@Order设置大小了,order越小越线执行
而我那上面那个问题我猜测是由于AOP种事务的顺序和AOP日志输出的顺序先后搞混了(虽然事务的order是最大的,但是由于AOP日志输出也没设置order),因此我这只AOP日志输出的order为1,然后发现居然可以了。虽然还没去验证是不是这个原因,但是至少先解决后在去深入了解原因!