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

在线问题事迹(1)数据库事务没有生效?

时间:2023-03-19 23:25:02 科技观察

Spring声明式事务为Javaers提供了一种方便的事务配置方式,结合SpringBoot自动配置,基本上只要在方法上加上@Transactional注解就可以瞬间启动方法的事务性配置。但是只是在方法上加上@Transactional注解,你觉得这样就够了吗?如果事务处理不当,一般不会导致服务停止,测试阶段不易重现。但是,随着系统业务越来越复杂,会带来大量的数据不一致,随之而来的是大量的线上问题和人工对数据的巡检和维护。1你的Spring事务如何生效?在使用@Transactional开启声明式事务时,灵魂发问:事务有效吗?案例用户表实体类DAO层根据用户名查询所有数据@RepositorypublicinterfaceUserRepositoryextendsJpaRepository{ListfindByName(Stringname);}服务层UserService类负责业务逻辑处理,包括以下方法:createUserWrong1调用私有方法:createUserPrivate,注解为@Transactional。当传入的用户名包含test时,会抛出异常,导致创建用户操作失败,期望事务回滚:getUserCountController层调用刚刚定义的UserService中的入口方法createUserWrong1。测试结果表明,即使用户名无效,也可以成功创建用户。刷新浏览器多次发现非法用户注册。2@Transactional如何保证生效?除非有特殊配置(比如使用AspectJ静态织入实现AOP),只有定义在public方法上的@Transactional才能生效。Spring默认通过动态代理实现AOP,增强了目标方法,但是私有方法无法代理,自然也就无法动态增强事务处理逻辑。这很简单,只需将createUserPrivate方法更改为public即可。在UserService中再创建一个入口方法createUserWrong2调用这个public方法再试:.getMessage());}returnuserRepository.findByName(name).size();}//标记@Transactional的public方法@TransactionalpublicvoidcreateUserPublic(UserEntityentity){userRepository.save(entity);if(entity.getName().contains("test"))thrownewRuntimeException("invalidusername!");}新的createUserWrong2方法transaction也不生效。必须通过代理类从外部调用目标方法。要调用的增强方法必须是调用代理后的对象。尝试修改UserService,注入一个self,然后通过self实例调用标有@Transactional注解的createUserPublic方法。设置断点说明self是Spring通过CGLIB增强的类。CGLIB通过继承来实现代理类。私有方法在子类中不可见,自然无法进行事务增强;this指针代表的是对象本身,Spring是不可能注入this的,所以通过this的访问方式一定不能是代理。改成self,调用Controller中的createUserRight方法验证事务是否生效:非法用户注册操作可以回滚。虽然在UserService中注入自己的createUserPublic可以正确实现事务,但这不符合惯用法。比较合理的实现方式是让Controller直接调用之前定义的UserService的createUserPublic方法。@GetMapping("right2")publicintright2(@RequestParam("name")Stringname){try{userService.createUserPublic(newUserEntity(name));}catch(Exceptionex){log.error("createuserfailedbecause{}",ex.getMessage());}returnuserService.getUserCount(name);}本次自调用/自调用/Controller调用UserService本次自调用不能走Spring代理类。最后两次调用的Spring-injectedUserService只能通过代理调用createUserPublic方法进行动态增强。建议开发时打开相关Debug日志,了解Spring事务实现的细节。例如对于JPA数据库访问,可以这样开启Debug日志:注入的UserServiceBean。显然,因为这次调用没有经过代理,所以事务在createUserPublic中没有生效,只在Repository中保存://CallpubliccreateUserPublicthroughthisinUserService[23:04:30.748][http-nio-45678-exec-5][DEBUG][o.s.orm.jpa.JpaTransactionManager:370]-创建带有名称的新事务[org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT[DEBUG][o.s.orm.jpa.JpaTransactionManager:]-Creatingnewtransactionwithname[org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT//通过Controller中注入的UserServiceBean调用createUserPublic[10:10:47.750][http-nio-45678-exec-6][DEBUG][o.s.orm.jpa.JpaTransactionManager:370]-Creatingnewtransactionwithname[org.geekbang.time.commonmistakes.transaction.demo1.UserService.createUserPublic]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT这个实现在Contro中处理异常比较麻烦勒。不如直接用@Transactional注解createUserWrong2,然后在Controller中直接调用方法。这样不仅可以从外部(Controller中)调用UserService方法,而且方法是public的,可以通过动态代理AOP来增强。你为什么不试一试?让我们看看它是如何工作的。下次再分解~