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

业务篇:Spring业务的坑你踩过吗?

时间:2023-03-12 13:25:41 科技观察

这篇文章将我和同事在工作中踩过的关于事务的陷阱,以及踩坑后发现的使用Spring事务的陷阱,让大家避免踩坑坑。首先,我们来看看这些交易坑。一般来说,经常会遇到这四种:自调用、异常被吃掉、异常抛出类型错误、事务传播机制不熟悉。具体的例子,我们来看一下:1.数据库引擎不支持事务,一般估计不太可能出现这种笼统的估计。毕竟,如果要使用事务,肯定会从一开始就选择支持事务的数据库引擎。比如常用的oracle直接支持事务,而mysql的innodb支持事务,myIsam不支持事务。mysql5.1版本之前默认引擎是myIsam,以后版本默认是innodb~建议检查项目:mysql数据库引擎。执行命令:showvariableslike'%storage_engine%';可以看到我本地5.7版本的mysql数据库默认的数据库引擎是innodb。2.方法不public其实经过我自己的测试,除了private方法本身不能编译外,public、protected、default这三个修饰符都支持事务。有兴趣的也可以试试!3、同一个类中有两个方法methodA和methodB等自调用问题。methodA没有设置事务,methodB设置了事务,当methodA调用methodB时,事务会失效。1)同类方法调用/***自调用测试:事务失败,表中新增两条数据:id为10和11的数据*/@OverridepublicvoidtestInvokeBInOneClass(){Useruser=User.builder().id(10).name("王二").age(22).build();userDao.addUser(用户);testB();}@TransactionalpublicvoidtestB(){用户用户=用户。builder().id(11).name("张三").age(22).build();userDao.addUser(用户);inti=1/0;}我们来预测一下:如果事务失败,数据库Zhong会成功添加两条数据:王二和张三。如果事务生效,则不会向表中添加任何数据。执行该方法后,我们会发现数据库的t_user2表中的记录是:是的,结果是交易无效。2)不同类中的方法调用我们把testB()方法放到了另一个类TransactionBImpl中。此时,调用TransactionBImpl类中的testB()方法;/***自调用测试:事务生效,表中新增一条数据:id为10的数据*/@OverridepublicvoidtestInvokeBInTwoClass(){Useruser=User.builder().id(10).name("王二").age(22).build();userDao.addUser(用户);transactionB.testB();}发现数据库中的数据是:descriptiontransaction有效,为什么?因为外层的testInvokeBInTwoClass()方法本身没有事务(没有事务注解),它调用了另一个有事务注解的类中的testB()方法,别忘了@Transactional注解的默认传播机制是PROPAGATION_REQUIRED——如果有没有事务,你必须自己创建一个新的事务。也就是说,最终的效果是testB()方法在一个事务里面,testInvokeBInTwoClass()方法里面没有事务(不会因为异常触发回滚操作)。那么,最后的结果就很容易理解了~如果你听说过独立事务,可以想到它的实现机制!Tips有些业务需求要求methodA调用methodB时,methodB执行失败不影响调用前的操作。如果在表中注册一个调用前的状态日志,并且不想因为调用失败回滚这条记录,可以这样写~总结当一个事务发生自调用时,如果调用者不加@Transactional注解,事务将失效。为了使事务生效,可以考虑将调用的方法放在另一个类中。4、不支持事务比较好理解,但是在编码过程中容易被忽略,所以这里也提一下。当methodA在另一个类中调用methodB时,如果methodB将事务传播机制设置为Propagation.NOT_SUPPORTED。那么,即使methodA启动了事务,也不一定会按照你的预期发展。看下面的例子:UserServiceImplclass@OverridepublicvoidtestNotSupported(){Useruser=User.builder().id(10).name("王二").age(22).build();userDao.addUser(用户);transactionB.testNotSupported();}TransactionBImplclass@Transactional(propagation=Propagation.NOT_SUPPORTED)@OverridepublicvoidtestNotSupported(){Useruser=User.builder().id(11).name("张三").age(22).build();userDao.addUser(用户);诠释我=1/0;即,UserServiceImpl类中的testNotSupported()方法调用了TransactionBImpl类中的testNotSupported()方法。我们来分析一下。根据调用者是否开启事务,可以分为以下两种情况:1)如果调用者的testNotSupported()方法没有添加@Transactional注解,表中数据为:明显,说明两种方法是统一的。事务。如果添加,则只插入一条数据。说明外部方法中还有一个事务,只要出现异常就会回滚。但是调用方transactionB.testNotSupported()方法内部不支持事务,所以方法失败后不会有事务回滚,所以错误前的插表操作不会回滚。5.异常被捕获,没有被抛出。由于事务默认回滚:RuntimeException和Error,下面两种情况会失效。1)异常被吃掉,事务无效/***7.异常被吃掉:尝试异常(不抛出),事务无效*/@Transactional@OverridepublicvoidtestException(){try{用户用户=用户。builder().id(10).name("王二").age(22).build();userDao.addUser(用户);诠释我=1/0;}catch(Exceptione){System.out.println("执行失败:"+e.getMessage());//thrownewRuntimeException("执行失败,抛出异常:"+e.getMessage());}}也就是说,异常并没有抛出来,而是通过catch活下来,然后再做一些其他的逻辑处理,这种事务是不会生效的。我们来看第二种情况。2)抛出Exception,交易失败@Transactional@OverridepublicvoidtestException()throwsException{try{Useruser=User.builder().id(10).name("王二").age(22).建造();userDao.addUser(用户);诠释我=1/0;}catch(Exceptione){System.out.println("执行失败:"+e.getMessage());thrownewException("ThrowAnExceptionisthrown:"+e.getMessage());//thrownewRuntimeException("执行失败,抛出异常:"+e.getMessage());}}回想一下我们的大前提:Spring事务默认回滚是:RuntimeException和Error两种情况。现在抛出Excption,不会触发事务的回滚,所以事务不会生效。这样的交易如何生效?改成抛出RuntimeException事务就可以生效了~现在可以试试。对了,如果你想触发其他异常的回滚,包括你自己定义的异常或者Exception异常,也是没有办法的。在方法的注解上配置rollbackFor属性即可,如:@Transactional(rollbackFor=Exception.class)。留给大家一个思考问题:如果配置了其他的例外,会不会覆盖原来的规则?综上,只把握一点:事务默认执行回滚操作有两种情况:RuntimeException和Error。因此,1)异常被捕获,不抛出则不会生效。2)如果抛出RuntimeException或者没有遇到Error,默认事务不会生效。那么,如何处理才能让交易生效,一定是显而易见的?6.没有启用spring事务管理功能。@EnableTransactionManagement注解用于让spring事务自动管理事务。只有这个注解,别忘了把这个注解写上去。但是当介绍;spring-boot-starter-jdbc不能自己写,为什么?让我们来看看;@EnableTransactionManagement这个注解启动事务,其实和我们自己使用@EnableTransactionManagement是一样的。因此,我们只要在SpringBoot中引入spring-boot-starter-jdbc依赖后,只需要使用@Transactional即可。2.总而言之,本文在上一篇的事务基础上,演示了几种在开发过程中容易出现事务失败的场景,或者事务无法按照你的预期执行。综上所述,日常生活中最容易发生事务失败或无法按预期执行的情况大致可以分为四类:自调用、异常被吃掉、异常抛出类型错误、事务传播机制不对熟悉的。那么我们需要如何避免踩坑,正确高效的使用交易呢?很简单,我们只需要关注涉及单个方法时事务的回滚机制,调用两个或多个方法时事务的传播机制,以及Spring业务的原理。对于单个方法调用,事务只有在执行过程中出现RuntimeException和Error,事务超时时才会回滚事务;多个方法:在同一个类中调用方法时,如果不使事务失效,需要在调用者的方法中全部注解事务,同时需要注意传播机制各层方法的事务和事务回滚;如果不属于同一个类,则需要根据具体的业务场景选择不同的传播机制。