当前位置: 首页 > 科技观察

Spring事务失败怎么办?

时间:2023-03-12 18:26:23 科技观察

这是朋友在微信上问的问题:这个问题比较典型,让我想起了面试时的一个Spring事务失败问题,原因和解决方法如出一辙。与您的朋友分享。一、AOP的原理朋友都知道AOP的底层是动态代理。动态代理的实现方式有两种:JDK动态代理:利用拦截器(必须实现InvocationHandler)和反射机制生成代理接口的匿名类,在具体方法之前调用CallInvokeHandler进行处理。比如假设有一个接口A,A有一个实现类B,现在要为B生成一个代理对象,那么实际上是为A接口自动生成一个匿名实现类,在这个匿名中调用B中的实现类方法。CGLIB动态代理:利用ASM框架加载代理对象类生成的class文件,修改其字节码生成子类进行处理。比如现在有一个类A,A没有接口,现在要为A生成一个代理对象,那么其实就是为A自动生成了一个子类,A中的方法都覆盖在这个子类中,所以各位朋友请注意,A类和里面的方法不能是final类型的,否则无法生成代理。如果被代理对象有接口,可以使用JDK动态代理,没有接口可以使用CGLIB动态代理。在Spring中,默认情况下,如果被代理对象有接口,则使用JDK动态代理,如果被代理对象没有接口,则使用CGLIB动态代理。在SpringBoot中,2.0之前的规则与Spring相同,2.0之后统一使用CGLIB动态代理。但是,这些是默认规则。如果有接口,但是想使用CGLIB动态代理,也可以通过修改配置来实现:如果是XML配置,想使用CGLIB动态代理,可以实现如下:。..如果想在Java配置中使用CGLIB动态代理,可以这样实现:@Component@Aspect@EnableAspectJAutoProxy(proxyTargetClass=true)publicclassLogAspect{}当然,在新版本的SpringBoot工程中,有接口的类默认使用CGLIB动态代理。但是此时如果想对有接口的类使用JDK动态代理,可以配置如下:spring.aop.proxy-target-class=false关于SpringBoot中的AOP代理问题,可以参考宋哥去年写的文章:SpringBoot中的AOP到底是JDK动态代理还是Cglib动态代理?.2.实际上课以第一节讲解为准。朋友们都知道,当你在项目中使用AOP时,你看到的类并不是原来的类。虽然他们解决的问题不同,但是他们有一个共同点,就是都是通过自定义注解+AOP来解决问题。下面就一步步教大家玩转多数据源动态切换!作为一个例子,让我告诉你动态代理是怎么回事。没有看过本文的朋友可以先看看。朋友们看一下,我的UserService大致如下:@ServicepublicclassUserService{@AutowiredUserMapperuserMapper;@DS("master")publicIntegercount(){returnuserMapper.getCount();}}朋友们看到了,在count()方法上加了@DS注解,这样以后这个count()方法就会被自动代理了。也就是说,当你将UserService注入到另一个类中时,它实际上并不是UserService。DEBUG的小伙伴们看看:从图中可以看出,我此时注入的UserService并不是真正的UserService。它是通过CGLIB动态代理为UserService生成的一个子类。该子类中的count方法逻辑上大致类似如下(其实就是AOP中的代码,具体可以参考手册,手把手教你玩多数据源动态切换!一文):#切换数据源#去数据库查询count#清除ThreadLocal中的变量#...但是,如果我的调用逻辑是这样的:@ServicepublicclassUserService{@AutowiredUserMapperuserMapper;publicIntegercount2(){返回计数();}@DS("master")publicIntegercount(){returnuserMapper.getCount();}}看一下,此时在count2方法中直接调用了count2方法,当然,count2()方法中的调用也可以写成this.count();,这样更清晰。我们使用当前对象调用count方法,当前对象不包含代理对象中的代码。我们通过DEBUG来看:那么,当我们直接调用count2中的count方法时,count方法中添加的注解就会失效。3.问题解决这个问题在所有使用AOP的地方都存在,存在的原因在第二小节已经分析清楚了。其实有很多解决办法。最简单最省事的就是在当前类中注入一个代理对象,然后通过代理对象调用其他方法,如下:@ServicepublicclassUserService{@AutowiredUserMapperuserMapper;@Autowired用户服务用户服务;publicIntegercount2(){返回userService.count();}@Transactional@DS("master")publicIntegercount(){returnuserMapper.getCount();}}虽然解决了问题,但毕竟不是一个好的解决方法(因为你自己注入了新版本的SpringBoot,需要开启循环依赖才能实现),在实际开发中,你应该试试从设计上避免这种问题。好了,这个问题说的很清楚了,交易失败的问题就不用我多说了吧!