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); // 回滚事务
}
}
从上面代码可以看出,我们的解决方案是使用了编程式事务。
参考文章
- 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方法中嵌套事务及事务失效的处理方法
还没有评论,来说两句吧...