点击上方“JavaEdge”,关注公众号
设为“ 星标”,好文章不错过!Spring声明式事务提供给 Javaer 们方便的事务配置方式,再搭配Spring Boot自动配置,基本只需在方法上添加@Transactional
注解,即可瞬间开启方法的事务性配置。
但仅为方法添加@Transactional
注解
你就以为这就够了吗?
事务未被正确处理,一般不会导致停止服务,更不易在测试阶段复现。但随系统业务越来越复杂,就会带来大量数据不一致问题,随后就是大量线上问题而后人工排查检修数据。
1 你的Spring事务怎么才算生效?
使用@Transactional
开启声明式事务时, 灵魂发问:事务生效了吗?
案例
用户表实体类
DAO 层
根据username查询所有数据
@Repositorypublic interface UserRepository extends JpaRepository {
List findByName(String name);}
Service层
负责业务逻辑处理,包括如下方法:
Controller层
调用一下刚才定义的UserService中的入口方法createUserWrong1
。
测试结果
即便用户名不合法,用户也能创建成功。刷新浏览器,多次发现非法用户注册。
2 @Transactional怎么确保生效?
除非特殊配置(比如使用AspectJ静态织入实现AOP),否则只有定义在public方法上的@Transactional才能生效。
Spring默认通过动态代理实现AOP,对目标方法增强,private方法无法代理到,自然也无法动态增强事务处理逻辑。
那简单,把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方法@Transactionalpublic void createUserPublic(UserEntity entity) {
userRepository.save(entity);if (entity.getName().contains("test"))throw new RuntimeException("invalid username!");}
新的createUserWrong2方法事务同样不生效。
要调用增强过的方法必然是调用代理后的对象。
尝试修改UserService,注入一个self,然后再通过self实例调用标记有@Transactional注解的createUserPublic方法。设置断点可以看到,self是由Spring通过CGLIB方式增强过的类。
CGLIB通过继承方式实现代理类,private方法在子类不可见,自然也就无法进行事务增强;
this指针代表对象自己,Spring不可能注入this,所以通过this访问方法必然不是代理。把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自调用
无法走到Spring代理类
后两种
调用的Spring注入的UserService,通过代理调用才有机会对createUserPublic方法进行动态增强。
推荐在开发时打开相关Debug日志,以了解Spring事务实现的细节。
比如JPA数据库访问,可以这么开启Debug日志:logging.level.org.springframework.orm.jpa=DEBUG
开启日志后再比较下在UserService中this调用、Controller中通过注入的UserService Bean调用createUserPublic的区别。
很明显,this调用因没走代理,事务没有在createUserPublic
生效,只在Repository的save
生效:
// 在UserService中通过this调用public的createUserPublic[23:04:30.748] [http-nio-45678-exec-5] [DEBUG] [o.s.orm.jpa.JpaTransactionManager:370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT[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增强。要不你试试?看看效果如何,下回分解~
往期推荐
阿里P8架构师教你kill代码重复/大量ifelse
一文讲清RedisCluster
阿里Java架构师教你写代码-如何校验参数?
你真的深知JWT(JSON Web Token)了吗?
OAuth 2.0实战(一)-通俗光速入门
目前交流群已有 800+人,旨在促进技术交流,可关注公众号添加笔者微信邀请进群
喜欢文章,点个“在看、点赞、分享”素质三连支持一下~