20%的业务代码的Spring声明式事务,可能都没处理正确

ADS谐波平衡仿真自动优化报错?三步搞定HB控件设置(附常见错误排查) 本文针对ADS谐波平衡仿真在自动优化中常见的报错问题,提供了三步核心排查法。首先检查并禁用HB控件中可能导致冲突的小信号仿真模式,其次合理化仿真频率与扫描参数设置,最后审视优化变量与目标的定义。通过精准配置HB控件以适配自动优化流程,能有效解决大多数仿真失败问题,确保非线性电路设计顺利进行。 阅读详情

今天,我来和你聊聊业务代码中与数据库事务相关的坑。

Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API (JPA)等事务API,实现了一致的编程模型,而Spring的声明式事务功能更是提供了极其方便的事务配置方式,配合Spring Boot的自动配置,大多数Spring Boot项目只需要在方法上标记@Transactional注解,即可一键开启方法的事务性配置。

据我观察,大多数业务开发同学都有事务的概念,也知道如果整体考虑多个数据库操作要么成功要么失败时,需要通过数据库事务来实现多个操作的一致性和原子性。但,在使用上大多仅限于为方法标记@Transactional,不会去关注事务是否有效、出错后事务是否正确回滚,也不会考虑复杂的业务代码中涉及多个子业务逻辑时,怎么正确处理事务。

事务没有被正确处理,一般来说不会过于影响正常流程,也不容易在测试阶段被发现。但当系统越来越复杂、压力越来越大之后,就会带来大量的数据不一致问题,随后就是大量的人工介入查看和修复数据。

所以说,一个成熟的业务系统和一个基本可用能完成功能的业务系统,在事务处理细节上的差异非常大。要确保事务的配置符合业务功能的需求,往往不仅仅是技术问题,还涉及产品流程和架构设计的问题。今天这一讲的标题“20%的业务代码的Spring声明式事务,可能都没处理正确”中,20%这个数字在我看来还是比较保守的。

我今天要分享的内容,就是帮助你在技术问题上理清思路,避免因为事务处理不当让业务逻辑的实现产生大量偶发Bug。

小心Spring的事务可能没有生效

在使用@Transactional注解开启声明式事务时, 第一个最容易忽略的问题是,很可能事务并没有生效。

实现下面的Demo需要一些基础类,首先定义一个具有ID和姓名属性的UserEntity,也就是一个包含两个字段的用户表:



@Entity @Data public class UserEntity { @Id @GeneratedValue(strategy = AUTO) private Long id; private String name; public UserEntity() { } public UserEntity(String name) { this.name = name; } }

为了方便理解,我使用Spring JPA做数据库访问,实现这样一个Repository,新增一个根据用户名查询所有数据的方法:



@Repository public interface UserRepository extends JpaRepository<UserEntity, Long> { List<UserEntity> findByName(String name); }

定义一个UserService类,负责业务逻辑处理。如果不清楚@Transactional的实现方式,只考虑代码逻辑的话,这段代码看起来没有问题。

定义一个入口方法createUserWrong1来调用另一个私有方法createUserPrivate,私有方法上标记了@Transactional注解。当传入的用户名包含test关键字时判断为用户名不合法,抛出异常,让用户创建操作失败,期望事务可以回滚:



@Service @Slf4j public class UserService { @Autowired private UserRepository userRepository; //一个公共方法供Controller调用,内部调用事务性的私有方法 public int createUserWrong1(String name) { try { this.createUserPrivate(new UserEntity(name)); } catch (Exception ex) { log.error("create user failed because {}", ex.getMessage()); } return userRepository.findByName(name).size(); } //标记了@Transactional的private方法 @Transactional private void createUserPrivate(UserEntity entity) { userRepository.save(entity); if (entity.getName().contains("test")) throw new RuntimeException("invalid username!"); } //根据用户名查询用户数 public int getUserCount(String name) { return userRepository.findByName(name).size(); } }

下面是Controller的实现,只是调用一下刚才定义的UserService中的入口方法createUserWrong1。



@Autowired private UserService userService; @GetMapping("wrong1") public int wrong1(@RequestParam("name") String name) { return userService.createUserWrong1(name); }

调用接口后发现,即便用户名不合法,用户也能创建成功。刷新浏览器,多次发现有十几个的非法用户注册。

这里给出@Transactional生效原则1,除非特殊配置(比如使用AspectJ静态织入实现AOP),否则只有定义在public方法上的@Transactional才能生效。原因是,Spring默认通过动态代理的方式实现AOP,对目标方法进行增强,private方法无法代理到,Spring自然也无法动态增强事务处理逻辑。

你可能会说,修复方式很简单,把标记了事务注解的createUserPrivate方法改为public即可。在UserService中再建一个入口方法createUserWrong2,来调用这个public方法再次尝试:



public int createUserWrong2(String name) { try { this.createUserPublic(new UserEntity(name)); } catch (Exception ex) { log.error("create user failed because {}", ex.getMessage()); } return userRepository.findByName(name).size(); } //标记了@Transactional的public方法 @Transactional public void createUserPublic(UserEntity entity) { userRepository.save(entity); if (entity.getName().contains("test")) throw new RuntimeException("invalid username!"); }

测试发现,调用新的createUserWrong2方法事务同样不生效。这里,我给出@Transactional生效原则2,必须通过代理过的类从外部调用目标方法才能生效。

Spring通过AOP技术对方法进行增强,要调用增强过的方法必然是调用代理后的对象。我们尝试修改下UserService的代码,注入一个self,然后再通过self实例调用标记有@Transactional注解的createUserPublic方法。设置断点可以看到,self是由Spring通过CGLIB方式增强过的类:

  • CGLIB通过继承方式实现代理类,private方法在子类不可见,自然也就无法进行事务增强;

  • this指针代表对象自己,Spring不可能注入this,所以通过this访问方法必然不是代理。

​

编辑切换为居中

添加图片注释,不超过 140 字(可选)

把this改为self后测试发现,在Controller中调用createUserRight方法可以验证事务是生效的,非法的用户注册操作可以回滚。

虽然在UserService内部注入自己调用自己的createUserPublic可以正确实现事务,但更合理的实现方式是,让Controller直接调用之前定义的UserService的createUserPublic方法,因为注入自己调用自己很奇怪,也不符合分层实现的规范:



@GetMapping("right2") public int right2(@RequestParam("name") String name) { try { userService.createUserPublic(new UserEntity(name)); } catch (Exception ex) { log.error("create user failed because {}", ex.getMessage()); } return userService.getUserCount(name); }

我们再通过一张图来回顾下this自调用、通过self调用,以及在Controller中调用UserService三种实现的区别:

​

编辑切换为居中

添加图片注释,不超过 140 字(可选)

通过this自调用,没有机会走到Spring的代理类;后两种改进方案调用的是Spring注入的UserService,通过代理调用才有机会对createUserPublic方法进行动态增强。

这里,我还有一个小技巧,强烈建议你在开发时打开相关的Debug日志,以方便了解Spring事务实现的细节,并及时判断事务的执行情况。

我们的Demo代码使用JPA进行数据库访问,可以这么开启Debug日志:



logging.level.org.springframework.orm.jpa=DEBUG

开启日志后,我们再比较下在UserService中通过this调用和在Controller中通过注入的UserService Bean调用createUserPublic区别。很明显,this调用因为没有走代理,事务没有在createUserPublic方法上生效,只在Repository的save方法层面生效:



//在UserService中通过this调用public的createUserPublic [10:10:19.913] [http-nio-45678-exec-1] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT //在Controller中通过注入的UserService Bean调用createUserPublic [10:10:47.750] [http-nio-45678-exec-6] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo1.UserService.createUserPublic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

你可能还会考虑一个问题,这种实现在Controller里处理了异常显得有点繁琐,还不如直接把createUserWrong2方法加上@Transactional注解,然后在Controller中直接调用这个方法。这样一来,既能从外部(Controller中)调用UserService中的方法,方法又是public的能够被动态代理AOP增强。

你可以试一下这种方法,但很容易就会踩第二个坑,即因为没有正确处理异常,导致事务即便生效也不一定能回滚。

事务即便生效也不一定能回滚

通过AOP实现事务处理可以理解为,使用try…catch…来包裹标记了@Transactional注解的方法,当方法出现了异常并且满足一定条件的时候,在catch里面我们可以设置事务回滚,没有异常则直接提交事务。

这里的“一定条件”,主要包括两点。

第一,只有异常传播出了标记了@Transactional注解的方法,事务才能回滚。在Spring的TransactionAspectSupport里有个 invokeWithinTransaction方法,里面就是处理事务的逻辑。可以看到,只有捕获到异常才能进行后续事务处理:



try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); }

第二,默认情况下,出现RuntimeException(非受检异常)或Error的时候,Spring才会回滚事务。

打开Spring的 DefaultTransactionAttribute类能看到如下代码块,可以发现相关证据,通过注释也能看到Spring这么做的原因,大概的意思是受检异常一般是业务异常,或者说是类似另一种方法的返回值,出现这样的异常可能业务还能完成,所以不会主动回滚;而Error或RuntimeException代表了非预期的结果,应该回滚:



/** * The default behavior is as with EJB: rollback on unchecked exception * ({@link RuntimeException}), assuming an unexpected outcome outside of any * business rules. Additionally, we also attempt to rollback on {@link Error} which * is clearly an unexpected outcome as well. By contrast, a checked exception is * considered a business exception and therefore a regular expected outcome of the * transactional business method, i.e. a kind of alternative return value which * still allows for regular completion of resource operations. * <p>This is largely consistent with TransactionTemplate's default behavior, * except that TransactionTemplate also rolls back on undeclared checked exceptions * (a corner case). For declarative transactions, we expect checked exceptions to be * intentionally declared as business exceptions, leading to a commit by default. * @see org.springframework.transaction.support.TransactionTemplate#execute */ @Override public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); }

接下来,我和你分享2个反例。

重新实现一下UserService中的注册用户操作:

  • 在createUserWrong1方法中会抛出一个RuntimeException,但由于方法内catch了所有异常,异常无法从方法传播出去,事务自然无法回滚。

  • 在createUserWrong2方法中,注册用户的同时会有一次otherTask文件读取操作,如果文件读取失败,我们希望用户注册的数据库操作回滚。虽然这里没有捕获异常,但因为otherTask方法抛出的是受检异常,createUserWrong2传播出去的也是受检异常,事务同样不会回滚。



@Service @Slf4j public class UserService { @Autowired private UserRepository userRepository; //异常无法传播出方法,导致事务无法回滚 @Transactional public void createUserWrong1(String name) { try { userRepository.save(new UserEntity(name)); throw new RuntimeException("error"); } catch (Exception ex) { log.error("create user failed", ex); } } //即使出了受检异常也无法让事务回滚 @Transactional public void createUserWrong2(String name) throws IOException { userRepository.save(new UserEntity(name)); otherTask(); } //因为文件不存在,一定会抛出一个IOException private void otherTask() throws IOException { Files.readAllLines(Paths.get("file-that-not-exist")); } }

Controller中的实现,仅仅是调用UserService的createUserWrong1和createUserWrong2方法,这里就贴出实现了。这2个方法的实现和调用,虽然完全避开了事务不生效的坑,但因为异常处理不当,导致程序没有如我们期望的文件操作出现异常时回滚事务。

现在,我们来看下修复方式,以及如何通过日志来验证是否修复成功。针对这2种情况,对应的修复方法如下。

第一,如果你希望自己捕获异常进行处理的话,也没关系,可以手动设置让当前事务处于回滚状态:



@Transactional public void createUserRight1(String name) { try { userRepository.save(new UserEntity(name)); throw new RuntimeException("error"); } catch (Exception ex) { log.error("create user failed", ex); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }

运行后可以在日志中看到Rolling back字样,确认事务回滚了。同时,我们还注意到“Transactional code has requested rollback”的提示,表明手动请求回滚:



[22:14:49.352] [http-nio-45678-exec-4] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :698 ] - Transactional code has requested rollback [22:14:49.353] [http-nio-45678-exec-4] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :834 ] - Initiating transaction rollback [22:14:49.353] [http-nio-45678-exec-4] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :555 ] - Rolling back JPA transaction on EntityManager [SessionImpl(1906719643<open>)]

第二,在注解中声明,期望遇到所有的Exception都回滚事务(来突破默认不回滚受检异常的限制):



@Transactional(rollbackFor = Exception.class) public void createUserRight2(String name) throws IOException { userRepository.save(new UserEntity(name)); otherTask(); }

运行后,同样可以在日志中看到回滚的提示:



[22:10:47.980] [http-nio-45678-exec-4] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :834 ] - Initiating transaction rollback [22:10:47.981] [http-nio-45678-exec-4] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :555 ] - Rolling back JPA transaction on EntityManager [SessionImpl(1419329213<open>)]

在这个例子中,我们展现的是一个复杂的业务逻辑,其中有数据库操作、IO操作,在IO操作出现问题时希望让数据库事务也回滚,以确保逻辑的一致性。在有些业务逻辑中,可能会包含多次数据库操作,我们不一定希望将两次操作作为一个事务来处理,这时候就需要仔细考虑事务传播的配置了,否则也可能踩坑。

请确认事务传播配置是否符合自己的业务逻辑

有这么一个场景:一个用户注册的操作,会插入一个主用户到用户表,还会注册一个关联的子用户。我们希望将子用户注册的数据库操作作为一个独立事务来处理,即使失败也不会影响主流程,即不影响主用户的注册。

接下来,我们模拟一个实现类似业务逻辑的UserService:



@Autowired private UserRepository userRepository; @Autowired private SubUserService subUserService; @Transactional public void createUserWrong(UserEntity entity) { createMainUser(entity); subUserService.createSubUserWithExceptionWrong(entity); } private void createMainUser(UserEntity entity) { userRepository.save(entity); log.info("createMainUser finish"); }

SubUserService的 createSubUserWithExceptionWrong实现正如其名,因为最后我们抛出了一个运行时异常,错误原因是用户状态无效,所以子用户的注册肯定是失败的。我们期望子用户的注册作为一个事务单独回滚,不影响主用户的注册,这样的逻辑可以实现吗?



@Service @Slf4j public class SubUserService { @Autowired private UserRepository userRepository; @Transactional public void createSubUserWithExceptionWrong(UserEntity entity) { log.info("createSubUserWithExceptionWrong start"); userRepository.save(entity); throw new RuntimeException("invalid status"); } }

我们在Controller里实现一段测试代码,调用UserService:



@GetMapping("wrong") public int wrong(@RequestParam("name") String name) { try { userService.createUserWrong(new UserEntity(name)); } catch (Exception ex) { log.error("createUserWrong failed, reason:{}", ex.getMessage()); } return userService.getUserCount(name); }

调用后可以在日志中发现如下信息,很明显事务回滚了,最后Controller打出了创建子用户抛出的运行时异常:



[22:50:42.866] [http-nio-45678-exec-8] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :555 ] - Rolling back JPA transaction on EntityManager [SessionImpl(103972212<open>)] [22:50:42.869] [http-nio-45678-exec-8] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :620 ] - Closing JPA EntityManager [SessionImpl(103972212<open>)] after transaction [22:50:42.869] [http-nio-45678-exec-8] [ERROR] [t.d.TransactionPropagationController:23 ] - createUserWrong failed, reason:invalid status

你马上就会意识到,不对呀,因为运行时异常逃出了@Transactional注解标记的createUserWrong方法,Spring当然会回滚事务了。如果我们希望主方法不回滚,应该把子方法抛出的异常捕获了。

也就是这么改,把 subUserService.createSubUserWithExceptionWrong包裹上catch,这样外层主方法就不会出现异常了:



@Transactional public void createUserWrong2(UserEntity entity) { createMainUser(entity); try{ subUserService.createSubUserWithExceptionWrong(entity); } catch (Exception ex) { // 虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。 log.error("create sub user error:{}", ex.getMessage()); } }

运行程序后可以看到如下日志:



[22:57:21.722] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo3.UserService.createUserWrong2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT [22:57:21.739] [http-nio-45678-exec-3] [INFO ] [t.c.transaction.demo3.SubUserService:19 ] - createSubUserWithExceptionWrong start [22:57:21.739] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :356 ] - Found thread-bound EntityManager [SessionImpl(1794007607<open>)] for JPA transaction [22:57:21.739] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :471 ] - Participating in existing transaction [22:57:21.740] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :843 ] - Participating transaction failed - marking existing transaction as rollback-only [22:57:21.740] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :580 ] - Setting JPA transaction on EntityManager [SessionImpl(1794007607<open>)] rollback-only [22:57:21.740] [http-nio-45678-exec-3] [ERROR] [.g.t.c.transaction.demo3.UserService:37 ] - create sub user error:invalid status [22:57:21.740] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :741 ] - Initiating transaction commit [22:57:21.740] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :529 ] - Committing JPA transaction on EntityManager [SessionImpl(1794007607<open>)] [22:57:21.743] [http-nio-45678-exec-3] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :620 ] - Closing JPA EntityManager [SessionImpl(1794007607<open>)] after transaction [22:57:21.743] [http-nio-45678-exec-3] [ERROR] [t.d.TransactionPropagationController:33 ] - createUserWrong2 failed, reason:Transaction silently rolled back because it has been marked as rollback-only org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only ...

需要注意以下几点:

  • 如第1行所示,对createUserWrong2方法开启了异常处理;

  • 如第5行所示,子方法因为出现了运行时异常,标记当前事务为回滚;

  • 如第7行所示,主方法的确捕获了异常打印出了create sub user error字样;

  • 如第9行所示,主方法提交了事务;

  • 奇怪的是,如第11行和12行所示,Controller里出现了一个UnexpectedRollbackException,异常描述提示最终这个事务回滚了,而且是静默回滚的。之所以说是静默,是因为createUserWrong2方法本身并没有出异常,只不过提交后发现子方法已经把当前事务设置为了回滚,无法完成提交。

这挺反直觉的。我们之前说,出了异常事务不一定回滚,这里说的却是不出异常,事务也不一定可以提交。原因是,主方法注册主用户的逻辑和子方法注册子用户的逻辑是同一个事务,子逻辑标记了事务需要回滚,主逻辑自然也不能提交了。

看到这里,修复方式就很明确了,想办法让子逻辑在独立事务中运行,也就是改一下SubUserService注册子用户的方法,为注解加上propagation = Propagation.REQUIRES_NEW来设置REQUIRES_NEW方式的事务传播策略,也就是执行到这个方法时需要开启新的事务,并挂起当前事务:



@Transactional(propagation = Propagation.REQUIRES_NEW) public void createSubUserWithExceptionRight(UserEntity entity) { log.info("createSubUserWithExceptionRight start"); userRepository.save(entity); throw new RuntimeException("invalid status"); }

主方法没什么变化,同样需要捕获异常,防止异常漏出去导致主事务回滚,重新命名为createUserRight:



@Transactional public void createUserRight(UserEntity entity) { createMainUser(entity); try{ subUserService.createSubUserWithExceptionRight(entity); } catch (Exception ex) { // 捕获异常,防止主方法回滚 log.error("create sub user error:{}", ex.getMessage()); } }

改造后,重新运行程序可以看到如下的关键日志:

  • 第1行日志提示我们针对createUserRight方法开启了主方法的事务;

  • 第2行日志提示创建主用户完成;

  • 第3行日志可以看到主事务挂起了,开启了一个新的事务,针对createSubUserWithExceptionRight方案,也就是我们的创建子用户的逻辑;

  • 第4行日志提示子方法事务回滚;

  • 第5行日志提示子方法事务完成,继续主方法之前挂起的事务;

  • 第6行日志提示主方法捕获到了子方法的异常;

  • 第8行日志提示主方法的事务提交了,随后我们在Controller里没看到静默回滚的异常。


[23:17:20.935] [http-nio-45678-exec-1] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo3.UserService.createUserRight]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT [23:17:21.079] [http-nio-45678-exec-1] [INFO ] [.g.t.c.transaction.demo3.UserService:55 ] - createMainUser finish [23:17:21.082] [http-nio-45678-exec-1] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :420 ] - Suspending current transaction, creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo3.SubUserService.createSubUserWithExceptionRight] [23:17:21.153] [http-nio-45678-exec-1] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :834 ] - Initiating transaction rollback [23:17:21.160] [http-nio-45678-exec-1] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :1009] - Resuming suspended transaction after completion of inner transaction [23:17:21.161] [http-nio-45678-exec-1] [ERROR] [.g.t.c.transaction.demo3.UserService:49 ] - create sub user error:invalid status [23:17:21.161] [http-nio-45678-exec-1] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :741 ] - Initiating transaction commit [23:17:21.161] [http-nio-45678-exec-1] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :529 ] - Committing JPA transaction on EntityManager [SessionImpl(396441411<open>)]

运行测试程序看到如下结果,getUserCount得到的用户数量为1,代表只有一个用户也就是主用户注册完成了,符合预期:

​

编辑切换为居中

添加图片注释,不超过 140 字(可选)

重点回顾

今天,我针对业务代码中最常见的使用数据库事务的方式,即Spring声明式事务,与你总结了使用上可能遇到的三类坑,包括:

第一,因为配置不正确,导致方法上的事务没生效。我们务必确认调用@Transactional注解标记的方法是public的,并且是通过Spring注入的Bean进行调用的。

第二,因为异常处理不正确,导致事务虽然生效但出现异常时没回滚。Spring默认只会对标记@Transactional注解的方法出现了RuntimeException和Error的时候回滚,如果我们的方法捕获了异常,那么需要通过手动编码处理事务回滚。如果希望Spring针对其他异常也可以回滚,那么可以相应配置@Transactional注解的rollbackFor和noRollbackFor属性来覆盖其默认设置。

第三,如果方法涉及多次数据库操作,并希望将它们作为独立的事务进行提交或回滚,那么我们需要考虑进一步细化配置事务传播方式,也就是@Transactional注解的Propagation属性。

可见,正确配置事务可以提高业务项目的健壮性。但,又因为健壮性问题往往体现在异常情况或一些细节处理上,很难在主流程的运行和测试中发现,导致业务代码的事务处理逻辑往往容易被忽略,因此我在代码审查环节一直很关注事务是否正确处理。

如果你无法确认事务是否真正生效,是否按照预期的逻辑进行,可以尝试打开Spring的部分Debug日志,通过事务的运作细节来验证。也建议你在单元测试时尽量覆盖多的异常场景,这样在重构时,也能及时发现因为方法的调用方式、异常处理逻辑的调整,导致的事务失效问题。

射频总集篇:(不断更新) 射频每一章涉及到的重点理论/设计方法 CHAPTER 1 传输线理论 输入阻抗Zin,负载阻抗ZL,驻波比ρ,反射系数 CHAPTER 2 波导 矩形波导,圆波导,同轴线 CHAPTER 3 微型带状传输线 微带线的参数设计,带状线的参数设计 CHAPTER 4 ... 阅读详情

相关推荐

FairyGUI编辑器自定义菜单扩展插件

本文涉及到的软件有:FairyGUI,VSCode代码环境涉及到了:LuaVSCode插件:EmmyLua在编写FairyGUI编辑器菜单前,了解一下FairyGUIEditor的API会有效的帮助我们解决很多问题。FairyGUI的扩展是通过编辑器自带的插件功能实现的,插件中我使用的是lua环境模板。导入编辑器的LuaAPI,文件可以在FairyGUI-Editor源码的插件目录中找到。接下来将通过功能来说明对应的API作用。

Aili_Xiao的博客 4501

学习Java业务开发常见错误

当反射、注解和泛型遇到OOP时,会有哪些坑? 反射调用方法不是以传参决定重载:reflectionissue 泛型经过类型擦除多出桥接方法的坑:genericandinheritance 注解可以继承吗?:annotationinheritance (补充)内部类的例子:innerclass 全局异常处理类,以及全局返回值优化 全局返回值的设定及优化 异常的封装及优化 ......

weixin_44681259的博客 346

TCNOpen实现TRDP通信协议(源码编译运行测试)

概述 本文介绍了通过TCNOpen开源源码实现TRDP通信,包括源码下载,开发环境搭建,编译运行,测试。 Linux环境下编译运行TRDP (一) 下载CANOpen源码。 下载地址:https://sourceforge.net/p/tcnopen/trdp/HEAD/tree/ (二) 源码编译 下载源码解压过后,在根目录文件readme-makefile.txt有介绍源码的配置和编译方法。 在trdp/config目录下可以看到包含在linux环境下的编译配置文件LINUX_config;执行”m

fengweibo112的博客 9637

《开发实战》06 | 20%的业务代码的Spring声明式事务,可能都没处理正确

因为Spring默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到。默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring才会回滚事务。( otherTask 文件读取操作,文件不存在会抛出一个IOException,是受检异常,无法传播)只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚,必须通过代理过的类从外部调用目标方法才能生效。8、数据库本身不支持事务。7、没有配置事务管理器。

weixin_39924752的博客 147

OpenSceneGraph编译、安装、开发环境

详细描述WIndows 10下使用Visual Studio 2019编译OpenSceneGraph 3.6.5的操作流程

国产CAx(CAD/CAE/CAM)软件研发 8774

海量数据 / Vastbase 深度研究笔记 v3.1

v3.1 重点修订:在 v3.0 审慎版基础上,新增“2026 新变量”章节,纳入 7 亿级定增、HTAP / 多模态时序数据库、G100 V3.0 Build 8 的 3CM+2DN 高可用补强、pgvector / V100 / AI 多模叙事,以及集成服务基因与标准软件 ARR 的拉扯。核心关注点是:Vastbase 能否把 openGauss 生态红利、数据库产品矩阵和行业交付能力,转化为可持续、可复购、低边际成本的标准软件收入。海量不是没有业务基本盘,也不是马上失血致死;客户越多,服务债越大。

喝醉酒的小白 578

spring @Transactional 生效原则

@Transactional 生效原则 在spring项目中,通常用 @Transactional 来控制是否事务操作,需要注意是否事务生效。 @Transactional 生效原则 1,除非特殊配置(比如使用 AspectJ 静态织入实现 AOP),否则只有定义在 public 方法上的 @Transactional 才能生效。原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到, Spring 自然也无法动态增强事务处理逻辑。 @Transact

tqknight1984的博客 848

手把手教你学Simulink--基础储能管理场景实例:基于Simulink的锂电池健康状态(SOH)在线评估仿真

本文详细介绍了基于Simulink的锂电池健康状态(SOH)在线评估仿真方法。主要内容包括:1)SOH定义与量化指标(容量SOH、内阻SOH及综合SOH);2)基于Thevenin模型参数辨识和递推最小二乘法(RLS)的在线评估原理;3)100次充放电循环的仿真场景设置;4)Simulink建模步骤(主电路搭建、核心模块实现及信号流连接);5)仿真结果分析,显示SOH评估误差<2%,响应时间45秒,满足在线评估需求;6)提出多参数融合评估、深度学习辅助辨识等优化方向。该方法为储能系统寿命管理提供了有效

MHD0815的博客 182

Java 业务开发常见错误 100 例 -- 学习笔记

看到这里我对其中的第一点感受尤为深刻,依稀记得软件工程导论项目验收的前一晚上,我们匆匆忙忙的开始测试,所有业务都可以按照我们正常的逻辑完成预期的任务,但是如果按照非主流逻辑,程序的bug就变得层出不穷,例如:电话号码的填写,前后端都没有做校验,最后就导致电话号码可以是中文而不会报错。关于第二点,我的评价是和真正生产环境的文档比起来,之前我们自己造的文档只能叫电子垃圾,相比较而言真的没什么设计可言。绑定的任务对于线程池属性的偏好,如果希望减少任务间的相互干扰,考虑按需使用隔离的线程池。

m0_51561690的博客 948

华为OD机试真题 新系统 - WIFI设备网络规划 (Java/Py/C/C++/Js/Go)

本文整理华为 OD 机试新系统机考真题库,涵盖高频考点精析与完整题目内容,提供 C 语言、Java、C++、Python、Go、JavaScript 多语言源码实现,搭配详细解题思路讲解,并支持在线 OJ 刷题练习,助力高效备考华为 OD 机试新系统。

算法大师! 2018

基于综合阻抗特征的配电架空线路单相断线故障区段定位研究

本文提出了一种基于综合阻抗特征的配电架空线路单相断线故障区段定位方法。通过分析故障后正序、负序阻抗变化,构造综合阻抗判据实现精准定位。仿真结果表明:该方法能有效识别故障区段,差异度判据显著(故障区段达0.508,非故障区段<0.016);对过渡电阻(1-200Ω)和负荷变化具有强鲁棒性,定位准确率不受影响。相比传统方法,综合阻抗法放大了断线故障特征,解决了弱故障信号检测难题,为配电网安全运行提供了可靠的技术手段。

AI的内部世界的博客 81

你的业务代码中Spring声明式事务处理正确了吗?

本文已收录GitHub,更有互联网大厂面试真题,面试攻略,高效学习资料等 ——20%的业务代码的Spring声明式事务,可能都没处理正确 Spring 针对 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API(JPA) 等事务 API,实现了一致的编程模型,而 Spring 的声明式事务功能更是提供了极其方便的事务配置方式,配合 Spring Boot 的自动配置,大多数 Spring Boot 项目只需要在方法上标记 @T..

Java_supermanNO1的博客 544

SAR合成孔径雷达成像及处理(附带Matlab源代码)

总结:本文介绍了SAR合成孔径雷达的基本原理,并提供了使用Matlab实现SAR成像和处理的源代码示例。然后,在慢时间域进行方位压缩处理,通过计算每个接收通道的延迟和频移,将多次接收的信号叠加得到最终的成像结果。SAR成像的基本原理是通过接收雷达发射的脉冲信号的回波,并利用多次接收的信号进行叠加处理。以多普勒频移校正为例,根据雷达飞行速度和方位角,计算实际的多普勒频率,并对每个接收通道的数据进行频移校正,以消除因目标运动引起的频率偏移。通过调整参数和算法,可以实现不同的SAR成像和处理效果。

ByteJolt的博客 690

06 | 20%的业务代码的spring声明式事务,可能都没有处理正确

原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。2,

m0_66939928的博客 212

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

25%的业务代码的Spring声明式事务,可能都没处理正确

原因是,Spring默认通过动态代理的方式实现AOP,对目标方法进行增强,private方法无法代理到,Spring自然也无法动态增强事务处理逻辑。你可能会说,修复方式很简单,把标记了事务注解的createUserPrivate方法改为public即可。

Huangjiazhen711的博客 117

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

2成的业务代码的Spring声明式事务,可能都没处理正确

坑一:事务不生效├── private 方法 → 改成 public└── this 自调用 → 从外部调用,或注入自己坑二:不回滚├── 吞了异常 → 手动 setRollbackOnly 或重新抛出└── 受检异常 → rollbackFor = Exception.class坑三:子事务影响主事务└── 用 REQUIRES_NEW 开启独立事务坑原因修复private 方法代理不到改成 publicthis 调用绕过代理从外部调用吞了异常异常没传播受检异常。

2301_79404691的博客 821

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

为什么spring中的controller跳转出错_你的业务代码中Spring声明式事务处理正确了吗?

Spring 针对 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API(JPA) 等事务 API,实现了一致的编程模型,而 Spring 的声明式事务功能更是提供了极其方便的事务配置方式,配合 Spring Boot 的自动配置,大多数 Spring Boot 项目只需要在方法上标记 @Transactional 注解,...

weixin_39780962的博客 137

20%的业务代码可能暗藏风险,Spring事务深度分析

今天,我来和你聊聊业务代码中与数据库事务相关的坑。Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API (JPA)等事务API,实现了一致的编程模型,而Spring的声明式事务功能更是提供了极其方便的事务配置方式,配合Spring Boot的自动配置,大多数Spring Boot项目只需要在方法上标记@Transactional注解,即可一键开启方法的事务性配置。

资料免费分享,点击名片 87

犯了这些错误吗?20%的Spring声明式事务可能有问题

今天,我来和你聊聊业务代码中与数据库事务相关的坑。Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API (JPA)等事务API,实现了一致的编程模型,而Spring的声明式事务功能更是提供了极其方便的事务配置方式,配合Spring Boot的自动配置,大多数Spring Boot项目只需要在方法上标记@Transactional注解,即可一键开启方法的事务性配置。

资料免费分享,点击名片 196

Spring声明式事务揭秘:你的20%业务代码或许有疏漏

今天,我来和你聊聊业务代码中与数据库事务相关的坑。Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API (JPA)等事务API,实现了一致的编程模型,而Spring的声明式事务功能更是提供了极其方便的事务配置方式,配合Spring Boot的自动配置,大多数Spring Boot项目只需要在方法上标记@Transactional注解,即可一键开启方法的事务性配置。

Reische的博客 83

聊聊业务代码中与数据库事务相关的坑

Spring 针对 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API (JPA) 等事务 API,实现了一致的编程模型,而 Spring 的声明式事务功能更是提供了极其 方便的事务配置方式,配合 Spring Boot 的自动配置,大多数 Spring Boot 项目只需要在 方法上标记 @Transactional 注解,即可一键开启方法的事务性配置。 据我观察,大多数业务开发同学都有事务的概念...

rdhj5566的专栏 937
上一篇: 判等问题:程序里如何确定你就是你?
下一篇: 从应用到底层:36张图带你进入Redis世界(上)
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值