Spring事务回滚失败的原因
Spring事务管理机制的实现原理-动态代理
在spring中实现动态代理就两种方式,一是使用JDK实现InvocationHandler接口,二是使用CGlib实现MethodInterceptor接口
这里简单说一下两者的区别:
1.代理的对象要求不同:
JDK是通过代理类实现了的接口去控制类的,换句话说JDK是代理接口的
CGlib适用范围更广,能代理没有通过接口定义业务方法的类,即直接代理类的
2.两者实现的技术不同:
JDK采用反射机制调用委托代理类方法
CGlib通过字节码层面截取类的所有方法,采用类似索引的方式直接调用委托类方法
基于JDK的实现,这个代理类适用于任何接口的实现
public class TxHandler implements InvocationHandler {
private Object originalObject;
public Object bind(Object obj) {
this.originalObject = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
if (!method.getName().startsWith("save")) {
UserTransaction tx = null;
try {
tx = (UserTransaction) (new InitialContext().lookup("java/tx"));
result = method.invoke(originalObject, args);
tx.commit();
} catch (Exception ex) {
if (null != tx) {
try {
tx.rollback();
} catch (Exception e) {
}
}
}
} else {
result = method.invoke(originalObject, args);
}
return result;
}
}
先看这段代码
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
newProxyInstance方法根据传入的接口类型(obj.getClass.getInterfaces())动态构造一个代理类实例返回,这也说明了为什么动态代理实现要求所代理的对象一定要实现一个接口,这个代理类实例在内存中是动态构造的,它实现了传入的接口列表中所包含的所有接口。
再来分析以下代码:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
……
result = method.invoke(originalObject, args);
……
return result;
}
为什么spring的事务不用我们手动开启,因为别人在方法调用的前后就已经帮我们开启了事务,InvocationHandler.invoke方法将在被代理类的方法被调用之前触发。通过这个方法,我们可以在被代理类方法调用的前后进行一些处 理,如代码中所示,InvocationHandler.invoke方法的参数中传递了当前被调用的方法(Method),以及被调用方法的参数。同 时,可以通过method.invoke方法调用被代理类的原始方法实现。这样就可以在被代理类的方法调用前后写入任何想要进行的操作。
Spring的事务管理机制实现的原理,就是通过这样通过对调用方法前后加一些代码操作,帮你开启事务。
基于CGlib
CGlib也是,只不过实现的方式不同。GClib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截所有的父类方法的调用并顺势织入横切逻辑
相当于继承了被代理对象,然后重写了代理对象的方法,在方法逻辑前后加入开启事务的代理块,所以CGlib也是有限制的,因为继承所以它代理不了private、final、static修饰的方法
class CgProxy implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("输出语句1");
//参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法
//引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
Object obj= methodProxy.invokeSuper(o,objects);
System.out.println("输出语句2");
return obj;
}
}
结论:spring的AOP同时使用了这两种方式,底层会自行判断应该使用哪种
事务回滚失败
@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();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
// 这个事务的意思是如果前面方法有事务存在,会将前面事务挂起,再重启一个新事务
public void test2(){
}
最常见的就是类中的方法自调用,使用了this指针去调用了方法,这里的test1中的test2方法如果执行失败,是不会回滚的,原因就是你直接调用了方法,而不是用动态代理类去调用你的方法,spring的事务机制原理就是,开启事务部分的代码你交给了代理类去做所以你不用去主动去开启事务,现在你直接调用了方法,也没有开启事务,自然就不会回滚
避免这种情况的方法,你可以通过 @Autowired一个代理类,通过代理类去调用,也可以用前置增强和后置增强去使用事务逻辑
为什么AOP不能代理静态方法
因为在Java编程规范中“静态方法是可继承但不可被重写的”。
举个例子:A类有静态方法a(),B类继承A类,B类继承了A类的静态方法,所以B类可直接使用A类的静态方法。此时若在B类中尝试重写静态方法a(),新的静态方法a()将变成独属于B类的静态方法,而失去了原属于A类静态方法a()的继承关系。注意,静态方法是独属于当前类的,你若定义便失去了父类静态方法的继承关系,新的静态方法只与当前所属类挂钩!Spring AOP的CGLIB代理在于对父类方法的重写,而对静态方法的重写,会使其失去与父类静态方法的继承关系,违背了代理的核心目的,因此CGLIB直接排除了静态方法。