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

一篇文章,让你学会11种Spring失败场景

时间:2023-03-19 21:25:40 科技观察

其实网上关于spring事务失败场景的文章很多,参差不齐。在这里我只分享自己的感悟,时长约10分钟。让我在上图中介绍一下。1.访问权限问题交易方法需要定义为public,非public方法交易将无效。事务拦截器TransactionalInterceptor将在执行方法之前进行拦截。通过动态代理方法,如果是cglib,是jdk的intercept方法或者invoke方法间接调用AbstractFallbackTransactionAttributeSource类的getTransactionAttribute方法获取配置信息。附上源码图:进一步跟踪getTransactionAttribute方法,我们可以看到,spring返回的transaction对象对于非public修改方法为null,allowPublicMethodsOnly返回一个布尔false。2、方法底层是aop修饰的,意思是通过jdk或者cglib生成代理类,在代理类中实现事务功能。如果最后修改了方法,那么就会生成代理类。中不能重写该方法,导致添加事务失败。同样,如果是静态修改,也不能通过动态代理变成事务方法。3、内部方法调用简单来说就是一个方法在内部调用另一个方法,但是另一个方法有事务,同样会导致事务失败,因为这个调用是这个对象的方法,而不是另一个方法。这里的对象可以理解。如果你想在方法内部调用另一个方法并且有事务,你需要创建一个新的服务对象来保存它。}}@ServicepublicclassTmTrimServiceliImpl{@Transactional(rollbackFor=Exception.class)publicvoidgetTrimById(Longid){TmTrimVOtmTrimVO=(newT);}}这样,通过创建一个新的服务方法,就可以将事务添加到新创建的服务方法中。说到这里,小伙伴们可能会觉得这样有点麻烦,那么有没有什么不创建新方法的方法呢?答案是肯定的,就是自己注入,利用springioc内部的三级缓存机制,这里自己注入是很容易的很好保证不会出现循环依赖:@ServicepublicclassTmTrimServicebackImpl{@AutowiredprivateTmTrimServicebackImpltmTrimServicebackImpl;publicvoidgetById(Longid){tmTrimServicebackImpl.getTrimById(id);}@Transactional(rollbackFor=Exception.class)publicvoidgetTrimById(Longid){TmTrimVOtmTrimVO=newTmTrimVO();}}其实说到这里,我还是觉得有点不好看。并不是说上面的代码有什么问题。我只是认为它可以使上面的代码看起来更好。有没有?答案是肯定的,那是什么?这不得不佩服spring强大完美的支持,那就是AopContext.currentProxy(),这个是创建一个代理类,切入方法调用调用前后,这个代理类对象保存在ThreadLocal中,所以调用通过这个代理类对象的交易方法才会生效。@ServicepublicclassTmTrimServicebackImpl{publicvoidgetById(Longid){((TmTrimServicebackImpl)AopContext.currentProxy()).getTrimById(id);}@Transactional(rollbackFor=Exception.class)publicvoidgetTrimById(Longid){TmTrimVOtmTrimVO=新的TmTrimVO();}}这样看来,代码就优雅多了,哈哈!!!4.不受springtransaction管理。这里需要明确一个前提,这是使用springtransaction的前提,即对象必须由spring管理,需要创建bean实例。在开发中,我们通过@Controller、@Service、@Component、@Repository等注解来自动实现依赖注入实例化的功能,但是如果是在对应的控制层、业务层、数据层的话忘记了添加相应的注解,也会失败。因为没有交给spring管理,比如:5.多线程调用回想一下几年前配置事务管理器的时候,会有这么一个配置:通过这个配置,你也可以知道,其实,springtransactions是多线程的,通过数据库连接transactionsconnection会导致持有的connetion不同。从网上找了一张图,通过这张图进一步理解:然后附上伪代码,结合上图进一步理解:@Slf4j@ServicepublicclassUserService{@AutowiredprivateUserMapperuserMapper;@AutowiredprivateRoleService角色服务;@Transactionalpublicvoidadd(UserModeluserModel)throwsException{userMapper.insertUser(userModel);newThread(()->{roleService.doOtherThing();}).start();}}@ServicepublicclassRoleService{@TransactionalpublicvoiddoOtherThing(){System.out.println("保存角色表数据");}}另一个事务doOtherThing是在事务add方法中调用的,但是事务方法是在另一个线程中调用的,这样两个方法不在同一个线程中,获取到的数据库链接是不同的。它们是两个不同的交易。一旦doOtherThing出现异常,add方法是不可能回滚的。这里需要说明的是同一个事务,也就是说只有同一个数据库连接才能同时提交和回滚。如果是在不同的线程中,得到的数据库连接肯定是不同的,所以是不同的事务。6、多线程调用没什么好说的,就是innodb和myisam引擎的区别。在版本5之前,默认是myisam引擎。该引擎不支持事务。Innodb5以后版本支持事务。7、未开启的事务其实可能比较容易忽略,因为我们好像没有配置如何开启事务,确实如此。为什么?其实原因很简单。springboot项目已经由DataSourceTransactionManagerAutoConfiguration类默默提供。我们开始了交易。该类将加载spring.datasource配置文件以启动事务。如果是非springboot项目,需要在xml文件中手动配置事务管理器。类似这样来启动事务。8、错误传播特性使用@Transactional注解时,可以指定传播参数,用于指定事务的传播特性。只有required、requires_new和nested会创建一个新事务:@ServicepublicclassUserService{@Transactional(propagation=Propagation.NEVER)publicvoidadd(UserModeluserModel){saveData(userModel);更新数据(用户模型);}}像上面的Propagation.NEVER类型的传播特性是不支持事务的,如果有事务就会抛出异常。9.我自己吞下了异常@Slf4j@ServicepublicclassUserService{@Transactionalpublicvoidadd(UserModeluserModel){try{saveData(userModel);更新数据(用户模型);}catch(Exceptione){log.error(e.getMessage(),e);如果异常是手动try...catched而不是手动抛出,那么sring会认为程序异常,不会回滚。10.手动抛出其他异常更新数据(用户模型);}catch(Exceptione){log.error(e.getMessage(),e);抛出新的异常(e);}}}捕获异常并抛出异常,事务不会回滚,因为spring事务默认只会回滚RuntimeException(运行时异常)和Error(错误),对于普通Exception(非运行时异常),它不会回滚。在网上找了一张图:这里异常分为运行时异常和非运行时异常(ioException)。(Queryonly)method:@Transactional(propagation=Propagation.NOT_SUPPORTED)这里要提一下,如果是自定义异常,比如我自定义了一个DALException异常,那么应该是@Transactional(notRollbackFor=DALException。class),一旦抛出的异常不属于DALException异常,事务将不会生效。11、嵌套事务回滚过多。其实这有点像js中的冒泡事件。也许我只需要底部,但外窗事件也被触发了。想想这里的事务,原来是一样的,嵌套多了可能只想回滚对应的事务,就没必要回滚其他事务了。这可以通过try...catch来处理。出去。本文转载自微信公众号“小王子的古木屋”,可通过以下二维码关注。转载本文请联系小王子古木屋公众号。