Spring 的事务可能没有生效

58 阅读2分钟

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

在使用 @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();
     }
 }