线上问题事迹(一)数据库事务居然都没生效?

数据库
Spring声明式事务提供给 Javaer 们方便的事务配置方式,再搭配Spring Boot自动配置,基本只需在方法上添加@Transactional注解,即可瞬间开启方法的事务性配置。

[[352574]]

Spring声明式事务提供给 Javaer 们方便的事务配置方式,再搭配Spring Boot自动配置,基本只需在方法上添加@Transactional注解,即可瞬间开启方法的事务性配置。

  • 但仅为方法添加@Transactional注解

你就以为这就够了吗?

事务未被正确处理,一般不会导致停止服务,更不易在测试阶段复现。但随系统业务越来越复杂,就会带来大量数据不一致问题,随后就是大量线上问题而后人工排查检修数据。

1 你的Spring事务怎么才算生效?

使用@Transactional开启声明式事务时, 灵魂发问:事务生效了吗?

案例

用户表实体类

 

DAO 层 

根据username查询所有数据

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

Service层

UserService类

负责业务逻辑处理,包括如下方法:

createUserWrong1调用private方法:

createUserPrivate,被@Transactional注解。当传入的用户名包含test则抛异常,让用户的创建操作失败,期望事务回滚: 


getUserCount

 

Controller层 

调用一下刚才定义的UserService中的入口方法createUserWrong1。 


测试结果

即便用户名不合法,用户也能创建成功。刷新浏览器,多次发现非法用户注册。

2 @Transactional怎么确保生效?

除非特殊配置(比如使用AspectJ静态织入实现AOP),否则只有定义在public方法上的@Transactional才能生效。

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

那简单,把createUserPrivate方法改为public即可。

在UserService中再建一个入口方法createUserWrong2,来调用这个public方法再次尝试:

  1. public int createUserWrong2(String name) { 
  2.     try { 
  3.         this.createUserPublic(new UserEntity(name)); 
  4.     } catch (Exception ex) { 
  5.         log.error("create user failed because {}", ex.getMessage()); 
  6.     } 
  7.     return userRepository.findByName(name).size(); 
  8.  
  9. //标记了@Transactional的public方法 
  10. @Transactional 
  11. public void createUserPublic(UserEntity entity) { 
  12.     userRepository.save(entity); 
  13.     if (entity.getName().contains("test")) 
  14.         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方法。

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

this自调用/self调用/Controller调用UserService


  • 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生效:

  1. // 在UserService中通过this调用public的createUserPublic 
  2. [23:04:30.748] [http-nio-45678-exec-5] [DEBUG] [o.s.orm.jpa.JpaTransactionManager:370 ] -  
  3. Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]:  
  4. PROPAGATION_REQUIRED,ISOLATION_DEFAULT 
  5.  
  6. [DEBUG] [o.s.orm.jpa.JpaTransactionManager       :370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 
  7. //在Controller中通过注入的UserService Bean调用createUserPublic 
  8. [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增强。要不你试试?看看效果如何,下回分解~

 

责任编辑:姜华 来源: JavaEdge
相关推荐

2020-11-18 08:32:07

数据库

2020-11-18 10:16:52

数据库回滚事务

2019-04-15 13:15:12

数据库MySQL死锁

2024-06-21 09:37:57

2009-09-24 14:12:22

Hibernate数据

2010-10-08 09:38:55

Android数据库事

2017-08-22 17:10:45

数据库MySQL事务模型

2024-05-28 00:00:30

Golang数据库

2020-06-17 16:56:36

数据库MySQL跨行事务

2021-10-03 15:00:44

数据库mysql单机

2019-05-13 08:24:58

数据库MySQLInnoDB

2020-08-20 07:37:21

数据库开源框架

2011-08-12 13:33:31

Oracle数据库自治事务

2023-10-11 08:09:53

事务隔离级别

2009-08-06 18:10:06

C#数据库事务

2018-07-20 11:10:21

数据库事务隔离性

2010-05-31 15:12:44

MySQL数据库

2018-09-06 14:53:39

数据库事务隔离隔离级别

2011-08-15 11:24:46

SQL Server事务

2022-08-01 20:29:48

分布式架构数据
点赞
收藏

51CTO技术栈公众号