1. 前言
Spring 针对 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API (JPA) 等事务 API,实现了一致的编程模型,我们大多数做业务开发的时候,通常就在业务方法上使用声明式注解 @Transactional 来开启事务,大多数我们就没有去关注事务是否会生效,出错后事务是否能正确回滚,所以这里是有“坑”的。
事务没有正确处理,对于我们来说通常是不易发现的,当压力越来越大数据越来越多的时候,极有可能带来大量的数据不一致脏数据的问题,所以处理好事务极为重要。
2. Spring事务没有生效
首先来看一下,Spring 在什么情况下事务是不生效的,在这里为了方便我直接就采用了Sping JPA 作为数据库访问,首先定义一个实体类
@Entity
@Data
@NoArgsConstructor
public class SysUser {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public SysUser(String name) {
this.name = name;
}
}
实现一个repository 接口,里面有一个根据名字查SysUser的方法
@Repository
public interface SysUserRepository extends JpaRepository<SysUser, Long> {
List<SysUser> findByName(String name);
}
实现一个SysUserService,其中使用一个公有方法调用标记了 @Transactional 注解的私有方法
@Service
@Slf4j
public class SysUserService {
@Resource
private SysUserRepository sysUserRepository;
/**
* 公有方法调用标记了 @Transactional 注解的私有方法
* @param name
* @return
*/
public int createUserWrong(String name) {
this.createUserPrivate(new SysUser(name));
return this.sysUserRepository.findByName(name).size();
}
@Transactional
private void createUserPrivate(SysUser sysUser) {
this.sysUserRepository.save(sysUser);
if (sysUser.getName().contains("test")) {
throw new RuntimeException("invalid name");
}
}
}
实现一个Controller如下
@RestController
@RequestMapping("/proxyfailed")
@RequiredArgsConstructor
public class ProxyFailedController {
private final SysUserService sysUserService;
@GetMapping("/wrong1")
public int wrong1(@RequestParam String name) {
return this.sysUserService.createUserWrong1(name);
}
}
测试接口可以发现,程序报异常了,但是数据库已经却成功的插入了记录,事务并未生效!!!


其实在上面已经看出来了,idea会在当你使用@Transactional 标记 private 修饰的方法时报红。
@Transactional生效的原则之一就是,只有定义在public方法上的 @Transactional 注解才能生效。这是因为Spring 默认使用动态代理实现AOP,对目标方法进行增强,private 修饰的方法是无法被代理到的。
那如果说,我把上面的 createUserPrivate 方法改为 public 修饰,那么事务是否会生效呢?
答案是否定的,事务是依然不会生效的。要使 @Transactional生效的原则之二就是,必须通过代理类从外部调用目标方法才能生效。在这里,使用this调用目标方法,this指向的并不是代理类,而是当前目标类实例。
在这里可以在目标Service类注入自己的Bean 实例,如下:
@Resource
private SysUserService sysUserService;
public int createUserRight1(String name) {
this.sysUserService.createUserPublic(new SysUser(name));
return this.sysUserRepository.findByName(name).size();
}

可以看到此时 this.sysUserService是通过cglib增强过的代理类实例,所以此时 @Transactional 注解是生效的。但是自己注入自己是一件很怪的事情,最好还是在controller中直接调用被 @Transactional标记的 public 方法,使事务生效,这里可以看到this.sysUserService同样是被增强后的代理类
那接下来我们看看下面这种情况, @Transactional有没有生效,也就是标记了 @Transactional的public方法调用private修饰的方法,且在private方法中进行了数据库操作
@GetMapping("/right3")
public int right3(@RequestParam String name) {
return this.sysUserService.createUserRight3(name);
}
@Transactional
public int createUserRight3(String name) {
this.createUserPrivate1(new SysUser(name));
return this.sysUserRepository.findByName(name).size();
}
private void createUserPrivate1(SysUser sysUser) {
this.sysUserRepository.save(sysUser);
if (sysUser.getName().contains("test")) {
throw new RuntimeException("invalid name");
}
}

