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

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

代理模式

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

public class UserService{
    ...
    public User getUserByName(String name) {
       return userDao.getUserByName(name);
    }
    ...
}

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

public class UserServiceProxy extends UserService{
    private UserService userService;
    ...
    public User getUserByName(String name){
        User user = null;
        try{
            // 在这里开启事务
            user = userService.getUserByName(name);
            // 在这里提交事务
        }
        catch(Exception e){
            // 在这里回滚事务

            // 这块应该需要向外抛异常, 否则我们就无法获取异常信息了. 
            // 至于方法声明没有添加异常声明, 是因为覆写方法, 异常必须和父类声明的异常"兼容". 
            // 这块应该是利用的java虚拟机并不区分普通异常和运行时异常的特点.
            throw e;
        }
        return user;
    }
    ...
}

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

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

Spring事务失效的原因

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

 - private、static、final的使用
 - 通过this.xxx()调用当前类的方法

还有的情况是:

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

Spring事务失效的解决方案

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

    @Autowired
    private TaskManageDAO taskManageDAO;

    @Transactional
    public void test1(){
        try {        
            this.test2();//这里调用会使事务失效,两条数据都会被保存
            /*           
            原因是:JDK的动态代理。
            在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象
            只有被动态代理直接调用的才会产生事务。
            这里的this是(TaskService)真实对象而不是代理对象
             */
             //解决方法
            TaskService proxy =(TaskService) AopContext.currentProxy();
            proxy.test2();
        }catch (Exception e){
            e.printStackTrace();
        }
        Task task = new Task();
        task.setCompleteBy("wjl练习1");
        task.setCompleteTime(new Date());
        taskManageDAO.save(task);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    // 这个事务的意思是如果前面方法有事务存在,会将前面事务挂起,再重启一个新事务
    public void test2(){
        Task task = new Task();
        task.setCompleteBy("wjl练习2");
        task.setCompleteTime(new Date());
        taskManageDAO.save(task);
        throw new RuntimeException();
    }
 }

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

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

@Service
public class TaskService {

    @Autowired
    private TaskManageDAO taskManageDAO;

    @Transactional
    public void test1(){
        try {        
            this.test2();//这里调用会使事务失效,两条数据都会被保存
            /*           
            原因是:JDK的动态代理。
            在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象
            只有被动态代理直接调用的才会产生事务。
            这里的this是(TaskService)真实对象而不是代理对象
             */
             //解决方法
            getService().test2();
        }catch (Exception e){
            e.printStackTrace();
        }
        Task task = new Task();
        task.setCompleteBy("wjl练习1");
        task.setCompleteTime(new Date());
        taskManageDAO.save(task);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    // 这个事务的意思是如果前面方法有事务存在,会将前面事务挂起,再重启一个新事务
    public void test2(){
        Task task = new Task();
        task.setCompleteBy("wjl练习2");
        task.setCompleteTime(new Date());
        taskManageDAO.save(task);
        throw new RuntimeException();
    }
    //解决事务失效
	private TaskService getService(){
		return SpringUtil.getBean(this.getClass());   //SpringUtil工具类见下面代码
	}
	
 }

SpringUtil工具类:

@Component
public class SpringUtil implements ApplicationContextAware {

	private static ApplicationContext applicationContext = null;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringUtil.applicationContext = applicationContext;
	}

	public static <T> T getBean(Class<T> cla) {
		return applicationContext.getBean(cla);
	}

	public static <T> T getBean(String name, Class<T> cal) {
		return applicationContext.getBean(name, cal);
	}

	public static Object getBean(String name){
		return applicationContext.getBean(name);
	}

	public static String getProperty(String key) {
		return applicationContext.getBean(Environment.class).getProperty(key);
	}
}
  • 使用默认的事务处理方式
    spring的事务默认是对RuntimeException进行回滚,而不继承RuntimeException的不回滚。因为在java的设计中,它认为不继承RuntimeException的异常是”checkException”或普通异常,如IOException,这些异常在java语法中是要求强制处理的。对于这些普通异常,spring默认它们都已经处理,所以默认不回滚。可以添加rollbackfor=Exception.class来表示所有的Exception都回滚。

  • 线程Thread中声明式事务不起作用

	@Override
	public void run() {
		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
		PlatformTransactionManager txManager = ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class);
		TransactionStatus status = txManager.getTransaction(def);
		try {
	           testDao.save(entity);
			   txManager.commit(status); // 提交事务
			} catch (Exception e) {
				System.out.println("异常信息:" + e.toString());
				txManager.rollback(status); // 回滚事务
			}						
	}

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

参考文章

  • 13
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值