06 | 20%的业务代码的Spring声明式事务,可能都没处理正确
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
本文通过具体案例和技术细节,提醒读者在业务代码中正确处理Spring声明式事务,以避免因事务处理不当而导致的业务逻辑Bug。作者指出,大多数开发人员对事务概念有所了解,但在实际应用中往往只是简单地在方法上标记@Transactional注解,而忽略了事务是否有效、异常情况下是否正确回滚等细节。文章展示了在使用@Transactional注解时可能出现的问题,并提出了解决方案。通过示例和技术细节,读者可以了解到如何正确处理Spring声明式事务,避免因事务处理不当而导致的业务逻辑Bug。文章内容涉及事务传播配置、异常处理、事务回滚等方面,对于开发人员在实际项目中遇到的事务处理问题具有一定的指导意义。文章还提到了在使用MyBatis配合Spring的声明式事务时需要注意的问题,以及针对private方法启用事务时的AOP配置建议。整体而言,本文为开发人员提供了关于Spring声明式事务处理的实用指南,有助于他们在项目开发中避免常见的事务处理陷阱。
《Java 业务开发常见错误 100 例》,新⼈⾸单¥59
全部留言(70)
- 最新
- 精选
- DarrenAspectJ与lombok,都是字节码层面进行增强,在一起使用时会有问题,根据AspectJ维护者Andy Clement的当前答案是由于ECJ(Eclipse Compiler for Java)软件包存在问题在AspectJ编译器基础结构中包含和重命名。 解决问题可以参考下面连接: http://aspectj.2085585.n4.nabble.com/AspectJ-with-Lombok-td4651540.html https://stackoverflow.com/questions/41910007/lombok-and-aspectj 分享一个使用lombok的坑: 之前为了set赋值方便,在VO或者DTO上使用了@Accessors(chain=true),这样就可以链式赋值,但是在动态通过内省获取set方法进行赋值时,是获取不到对应的set方法,因为默认的set方法返回值是void,但是加了@Accessors(chain=true)之后,set方法的返回值变成了this,这样通过内省就获取到对应的set方法了,通过去掉@Accessors(chain=true)即可实现,通过内省动态给属性赋值。
作者回复: 👍🏻
2020-03-23859 - hanazawakana否则只有定义在 public 方法上的 @Transactional 才能生效。这里一定要用public吗,用protected不行吗,protected在子类中应该也可见啊,是因为包不同吗
作者回复: 这个问题很好,首先JDK动态代理肯定是不行的只能是public,理论上CGLIB方式的代理是可以代理protected方法的,不过如果支持,那么意味着事务可能会因为切换代理实现方式表现不同,大大增加出现Bug的可能性,我觉得为了一致性所以Spring考虑只支持public,这是最好的。
2020-03-21447 - Seven.Lin澤耿我还遇到一个坑,就是子方法使用了REQUIRES_NEW,但是业务逻辑需要的数据是来源于父方法的,也就是父方法还没提交,子方法获取不到。当时的解决方法是把事务隔离级别改成RC,现在回想起来,不知道这种解决方法是否正确?
作者回复: 你说的隔离级别应该是指READ_UNCOMMITTED。我不认为这是很好的解决方案,子方法内需要依赖的数据来自父方法,可以方法传值,而不是用这种隔离级别。
2020-03-2242 - 看不到de颜色Spring默认事务采用动态代理方式实现。因此只能对public进行增强(考虑到CGLib和JDKProxy兼容,protected也不支持)。在使用动态代理增强时,方法内调用也可以考虑采用AopContext.currentProxy()获取当前代理类。
作者回复: 没错
2020-03-2925 - Seven.Lin澤耿老师,可以问一下为啥国内大多数公司使用MyBatis呢?是为了更加接近SQL吗?难倒国外业务不会遇到复杂的场景吗?
作者回复: 1、容易上手简单 2、国内BAT大厂对于Mybatis的使用量大,影响力大 3、国内大部分项目还是面向表结构的编程,从下到上的思考方式而非OOP的思考方式
2020-03-22825 - 九时四老师您好,有个数据库事务和spring事务的问题想请教下(我是一个入职半年的菜鸟)。 业务场景:为了实现同一个时间的多个请求,只有一个请求生效,在数据库字段上加了一个字段(signature_lock)标识锁状态。(没有使用redis锁之类的中间件,只讨论数据库事务和Spring的事务,以下的请求理解为同时请求) 1.在数据库层面,通过sql语句直接操作数据库,数据库事务隔离级别为可重复读: -- 请求1 show VARIABLES like 'tx_isolation'; START TRANSACTION; select * from subscribe_info where id = 29; -- update语句只有一个请求可以执行,另一个请求在等待 update trade_deal_subscribe_info set signature_lock =1 where id = 1 and signature_lock = 0; commit; -- 请求2 show VARIABLES like 'tx_isolation'; START TRANSACTION; select * from trade_deal_subscribe_info where id = 29; -- update语句只有一个请求可以执行,另一个请求在等待 update subscribe_info set signature_lock =1 where id = 1 and signature_lock = 0; commit; 两个请求中只有一个可以执行成功update语句,将signature_lock更新为1。 2.在代码层面按照在数据库层面的逻辑,service层的伪代码如下: public void test(ParamDto paramDto) { //取数据 Data data = getByParamDto(paramDto); // 尝试加锁,返回1表示加锁成功 Integer lockStatus = lockData(paramDto); // 加锁失败直接返回 if(!Objects.equals(1,lockStatus)){ return; } try{ // 处理业务代码,大概2到3秒 handle(); }catch(Exception e){ } finally{ // 释放锁 releaseLock(paramDto); } } 按照这样的方式,在方法上面不加注解的情况下,执行结果与在写sql的结果是一致的,两个请求只有一个可以执行成功;加上@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)之后,两个请求都可以拿到锁。 疑问是,Spring的事务和数据库的事务有什么关系,加上事务注解后,为什么和数据库的结果不一致。
作者回复: 如果要通过数据库来实现锁,那么加锁解锁,需要是单独的事务,不能跟业务的sql事务混合在一起,加锁和业务在一个事务里了,锁就没用了,因为每个事务里,都认为自己拿到了锁。
2020-03-21819 - 火很大先生@Transactional public int createUserRight(String name) throws IOException { try { userRepository.save(new UserEntity(name)); throw new RuntimeException("error"); } catch (Exception ex) { log.error("create user failed because {}", ex.getMessage()); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return userRepository.findByName(name).size(); } 请教老师,我这种写法,控制台打出了Initiating transaction rollback 但是数据库还是存上了数据,没有回滚,是因为findByName 这个查询语句的默认commit给提交了吗
作者回复: 需要明确几点: 1、我觉得这个事务最终是回滚的,你看到的这个查询有值,并不代表数据库有值 2、这个查询有值的原因是因为在一个事务内,此时事务并没有回滚,事务要到离开了这个createUserRight方法才会回滚(回想一下AOP原理) 3、在一个事务内肯定可以看到事务之前做的修改
2020-04-1013 - 王刚老师问个问题,您说得@Transactional事物回滚,只有是RuntimeException 或error时,才会回滚; 但是我在做测试时,发现@Transactional有一个rollbackFor属性,该属性可以指定什么异常回滚,如果@Transactional 不指定rollbackFor,默认得是RuntimeException?
作者回复: 是啊,所以我们才需要设置 @Transactional(rollbackFor = Exception.class) 来不仅仅回滚RuntimeException
2020-03-2626 - 汝林外史老师,创建主子用户那个业务,应该是子用户创建失败不影响主用户,但是主用户失败应该子用户也要回滚吧?如果是这样,那传播机制是不是应该用Propagation.NESTED
作者回复: 理论上NESTED显然是比两个独立都事务好,NESTED因为JPA Hibernate不支持,所以这里没有采用这种方式(而且对于本例而言,主用户创建在先,如果先出异常的话后面也不会到子用户的逻辑,所以问题不大),抽空我再传一个例子上去: https://github.com/JosephZhu1983/java-common-mistakes/tree/master/src/main/java/org/geekbang/time/commonmistakes/transaction/nested
2020-03-2446 - Yanni要注意,@Transactional 与 @Async注解不能同时在一个方法上使用, 这样会导致事物不生效。
作者回复: 能否分析一下原因给大家分享一下?
2020-04-1055