掌握 Spring 事务管理:深入理解 @Transactional 注解-事务失效

非 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的代理对象进行的,而不是直接在类内部调用。

上一篇:aws 小白入门,VPC 子网、路由表、互联网网关-六、回顾


下一篇:在Docker中部署osrm-backend