非 public
当被@Transactional
注解修饰的方法为非public
时,事务将失效。
@Service
@Slf4j
public class AdminService {
@Autowired
private AdminRepository adminRepository;
@Transactional
protected void saveAdmin(Admin admin) {
adminRepository.save(admin);
if (admin.getName().contains("@")) {
throw new RuntimeException("不合法");
}
}
}
在同包下新建一个测试类。
@Autowired
private AdminService adminService;
@GetMapping("/addAdminWrong")
public void add(@RequestParam("name") String name) {
Admin admin = new Admin();
admin.setName(name);
adminService.saveAdmin(admin);
}
测试接口发现,即使用户名不合法,用户也能创建成功。
@Transactional 生效原则(一):只有定义在 public 方法上的 @Transactional 才能生效。原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。
所以,将 saveAdmin
方法,修改为 public,就可以了。
自调用
当saveAdmin
方法是 public时,事务一定能生效吗?
答案是不一定,比如下面这个例子。
@Service
@Slf4j
public class AdminService {
@Autowired
private AdminRepository adminRepository;
public int addAdminWrong(String name) {
Admin admin = new Admin();
admin.setName(name);
try {
/**
* 一些其他业务处理
*/
this.saveAdmin(admin);
} catch (Exception e) {
log.error("添加失败:{}",e);
}
return adminRepository.findByName(name).size();
}
@Transactional
public void saveAdmin(Admin admin) {
adminRepository.save(admin);
if (admin.getName().contains("@")) {
throw new RuntimeException("不合法");
}
}
}
在上面代码中,我们新定义了一个addAdminWrong
方法,并在它内部调用了本类的saveAdmin
方法。
测试代码如下:
@GetMapping("/addAdminWrong")
public void add(@RequestParam("name") String name) {
adminService.addAdminWrong(name);
}
测试后发现,不合法的用户,还是被创建成功了。
Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
观察日志发现,自调用因为没有走代理,事务没有在 saveAdmin
方法上生效,只在 SimpleJpaRepository
上的 save 方法层面生效。
最简单的修改方案是,在AdminService
类中,自己注入自己,比如:
@Autowired
private AdminService self;
然后通过 self 实例去调用 self.saveAdmin(admin)
还有一种优雅的方案,是通过AopContext
在代理对象中获取自身。
比如:
AdminService adminService = (AdminService) AopContext.currentProxy();
adminService.saveAdmin(admin);
然后就会发现一个异常:
Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.
它的意思是:没有开启一个 exposeProxy
的属性,导致无法暴露出代理对象,从而无法获取。
所以我们在启动类上加上这个注解 @EnableAspectJAutoProxy(exposeProxy=true)
即可。
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy=true) //暴露代理对象
public class StarterDemoApplication {
public static void main(String[] args) {
SpringApplication.run(StarterDemoApplication.class, args);
}
}
然后,再观察日志,发现事务在AdminService.saveAdmin
方法上生效了
Creating new transaction with name [com.starter.demo.controller.AdminService.saveAdmin]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
@Transactional 生效原则(二):需要确保方法调用是通过Spring的代理对象进行的,而不是直接在类内部调用。