Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to 以及Spring事务失效的原因和解决方案

﹏ヽ暗。殇╰゛Y 2022-04-10 09:23 2233阅读 0赞

在讲述Spring事务失效的原因及解决方案之前,我们先回顾一下代理模式。

代理模式

我们知道, spring的声明式事务是基于代理模式的。那么说事务之前我们还是大致的介绍一下代理模式吧。 其实代理模式相当简单, 就是将另一个类包裹在我们的类外面, 在调用我们创建的方法之前, 先经过外面的方法, 进行一些处理, 返回之前, 再进行一些操作。比如:

  1. public class UserService{
  2. ...
  3. public User getUserByName(String name) {
  4. return userDao.getUserByName(name);
  5. }
  6. ...
  7. }

那么如果配置了事务, 就相当于又创建了一个类:

  1. public class UserServiceProxy extends UserService{
  2. private UserService userService;
  3. ...
  4. public User getUserByName(String name){
  5. User user = null;
  6. try{
  7. // 在这里开启事务
  8. user = userService.getUserByName(name);
  9. // 在这里提交事务
  10. }
  11. catch(Exception e){
  12. // 在这里回滚事务
  13. // 这块应该需要向外抛异常, 否则我们就无法获取异常信息了.
  14. // 至于方法声明没有添加异常声明, 是因为覆写方法, 异常必须和父类声明的异常"兼容".
  15. // 这块应该是利用的java虚拟机并不区分普通异常和运行时异常的特点.
  16. throw e;
  17. }
  18. return user;
  19. }
  20. ...
  21. }

然后我们使用的是 UserServiceProxy 类, 所以就可以”免费”得到事务的支持:

  1. @Autowired
  2. private UserService userService; // 这里spring注入的实际上是UserServiceProxy的对象
  3. private void test(){
  4. // 由于userService是UserServiceProxy的对象, 所以拥有了事务管理的能力
  5. userService.getUserByName("aa");
  6. }

Spring事务失效的原因

通过对Spring事务代理模式的分析,我们不难发现Spring事务失效的原因有以下几种情况:

  1. - privatestaticfinal的使用
  2. - 通过this.xxx()调用当前类的方法

还有的情况是:

  1. - 使用默认的事务处理方式
  2. - 线程Thread中声明式事务不起作用

Spring事务失效的解决方案

  • private、static、final的使用
    这一原因的解决方案很简单,我们只需要:不在类和方法上使用此类关键字即可。
  • 通过this.xxx()调用当前类的方法
    这一原因的解决方案如下:

    @Service
    public class TaskService {

    1. @Autowired
    2. private TaskManageDAO taskManageDAO;
    3. @Transactional
    4. public void test1(){
    5. try {
    6. this.test2();//这里调用会使事务失效,两条数据都会被保存
    7. /*
    8. 原因是:JDK的动态代理。
    9. 在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象
    10. 只有被动态代理直接调用的才会产生事务。
    11. 这里的this是(TaskService)真实对象而不是代理对象
    12. */
    13. //解决方法
    14. TaskService proxy =(TaskService) AopContext.currentProxy();
    15. proxy.test2();
    16. }catch (Exception e){
    17. e.printStackTrace();
    18. }
    19. Task task = new Task();
    20. task.setCompleteBy("wjl练习1");
    21. task.setCompleteTime(new Date());
    22. taskManageDAO.save(task);
    23. }
    24. @Transactional(propagation = Propagation.REQUIRES_NEW)
    25. // 这个事务的意思是如果前面方法有事务存在,会将前面事务挂起,再重启一个新事务
    26. public void test2(){
    27. Task task = new Task();
    28. task.setCompleteBy("wjl练习2");
    29. task.setCompleteTime(new Date());
    30. taskManageDAO.save(task);
    31. throw new RuntimeException();
    32. }

    }

我们仔细看上面的代码会发现:我们使用AopContext.currentProxy()生成了一个当前类的代理类,解决事务失效的问题。

如果使用上述方案报如下异常:Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available,可以采用下面的方案:

  1. @Service
  2. public class TaskService {
  3. @Autowired
  4. private TaskManageDAO taskManageDAO;
  5. @Transactional
  6. public void test1(){
  7. try {
  8. this.test2();//这里调用会使事务失效,两条数据都会被保存
  9. /*
  10. 原因是:JDK的动态代理。
  11. 在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象
  12. 只有被动态代理直接调用的才会产生事务。
  13. 这里的this是(TaskService)真实对象而不是代理对象
  14. */
  15. //解决方法
  16. getService().test2();
  17. }catch (Exception e){
  18. e.printStackTrace();
  19. }
  20. Task task = new Task();
  21. task.setCompleteBy("wjl练习1");
  22. task.setCompleteTime(new Date());
  23. taskManageDAO.save(task);
  24. }
  25. @Transactional(propagation = Propagation.REQUIRES_NEW)
  26. // 这个事务的意思是如果前面方法有事务存在,会将前面事务挂起,再重启一个新事务
  27. public void test2(){
  28. Task task = new Task();
  29. task.setCompleteBy("wjl练习2");
  30. task.setCompleteTime(new Date());
  31. taskManageDAO.save(task);
  32. throw new RuntimeException();
  33. }
  34. //解决事务失效
  35. private TaskService getService(){
  36. return SpringUtil.getBean(this.getClass()); //SpringUtil工具类见下面代码
  37. }
  38. }

SpringUtil工具类:

  1. @Component
  2. public class SpringUtil implements ApplicationContextAware {
  3. private static ApplicationContext applicationContext = null;
  4. @Override
  5. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  6. SpringUtil.applicationContext = applicationContext;
  7. }
  8. public static <T> T getBean(Class<T> cla) {
  9. return applicationContext.getBean(cla);
  10. }
  11. public static <T> T getBean(String name, Class<T> cal) {
  12. return applicationContext.getBean(name, cal);
  13. }
  14. public static Object getBean(String name){
  15. return applicationContext.getBean(name);
  16. }
  17. public static String getProperty(String key) {
  18. return applicationContext.getBean(Environment.class).getProperty(key);
  19. }
  20. }
  • 使用默认的事务处理方式
    spring的事务默认是对RuntimeException进行回滚,而不继承RuntimeException的不回滚。因为在java的设计中,它认为不继承RuntimeException的异常是”checkException”或普通异常,如IOException,这些异常在java语法中是要求强制处理的。对于这些普通异常,spring默认它们都已经处理,所以默认不回滚。可以添加rollbackfor=Exception.class来表示所有的Exception都回滚。
  • 线程Thread中声明式事务不起作用

    @Override

    1. public void run() {
    2. DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    3. def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    4. PlatformTransactionManager txManager = ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class);
    5. TransactionStatus status = txManager.getTransaction(def);
    6. try {
    7. testDao.save(entity);
    8. txManager.commit(status); // 提交事务
    9. } catch (Exception e) {
    10. System.out.println("异常信息:" + e.toString());
    11. txManager.rollback(status); // 回滚事务
    12. }
    13. }

从上面代码可以看出,我们的解决方案是使用了编程式事务。

参考文章

  • https://blog.csdn.net/andybbc/article/details/52913525
  • https://blog.csdn.net/weixin\_38319092/article/details/80919064
  • https://blog.csdn.net/rumengqiang/article/details/79917142
  • Spring方法中嵌套事务及事务失效的处理方法

发表评论

表情:
评论列表 (有 0 条评论,2233人围观)

还没有评论,来说两句吧...

相关阅读