java 事务异常处理
问题称述
一次在开发过程中,我需要用到事务;由于工程架构规则导致不能按照常用的事务处理方式。
Controller层代码示例
@RequestMapping(value = "/api/abc",metabchod=RequestMethod.POST)
public @ResponseBody ReturnModel postabc(@RequestBody PostabcModel account) {
return service.postabc(account,getIpAddr(requestThreadLocal.get()));
}
Service层代码示例
@Override
public ReturnModel postAccount(PostabcModel account,String ip) {
ReturnModel model = new ReturnModel();
try {
// 业务逻辑代码
} catch (Exception e) {
log("异常", e, model);
String content = "异常" + e.toString();
sendMessgeToDingGroup(content,1);
}
return model;
}
保持项目格式不变,Controller不能处理异常,但是我们知道,声明式事务 (基于注解@Transactional)的回滚是必须抛出运行异常(抛出的异常为RuntimeException的子类(Errors也会导致事务回滚))
问题分析
- 若回滚,必须抛出异常(抛出的异常为RuntimeException的子类(Errors也会导致事务回滚));
- Controller不能处理异常;
- 抛出的异常必须是运行是异常;
提出解决方案
方案一:
将业务代码单独抽出,写一个方法在service方法里面处理异常;代码如下:
@Override
public ReturnModel postAccount(PostabcModel account,String ip) {
ReturnModel model = new ReturnModel();
try {
transactionProcessing()
} catch (Exception e) {
log("异常", e, model);
String content = "异常" + e.toString();
sendMessgeToDingGroup(content,1);
}
return model;
}
@Transactional
public void transactionProcessing()throws Exception {
try {
// 业务逻辑代码
} catch (Exception e) {
logger.error("发生异常啦: ", e);
throw e;
}
}
方案二:
手动回滚
@Override
@Transactional
public ReturnModel postAccount(PostabcModel account,String ip) {
ReturnModel model = new ReturnModel();
try {
// 业务逻辑代码
} catch (Exception e) {
log("异常", e, model);
String content = "异常" + e.toString();
//手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return model;
}
总结问题及拓展
事务配置:
包扫描
<context:component-scan base-package="com.coinMall.service"/>
<!-- 事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
1.必须是public修饰
2.必须是service层接口实现类(不能调方法)
3.必须抛出运行时异常(RuntimeException……)
4.不需要throws Exception 使用这个就可以手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
解决问题:
方案一:
问题未解决,单独写一个方法,抛出异常并不会回滚;
失败原因
jdk的动态代理被代理对象必须实现接口,抽取出来的方法没有实现接口,导致事务处理的失败。
修改方案
采用CGLib动态代理,
方案二:
完美解决问题;
总结及拓展:
事务
ACID是原子性(atomicity)、一致性(consistency)、隔离性 (isolation)和持久性(durability)。
事务的原子性:表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。
事务的一致性:表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。
事务的隔离性:表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。
事务的持久性:表示已提交的数据在事务执行失败时,数据的状态都应该正确。
动态代理
我们都知道spring的事务处理用到了AOP的思想,而AOP是基于动态动态去实现的,显然我们所用到的事务(声明式事务)也是基于动态代理实现的
我们经常说的动态代理有两种,一种是基于jdk的动态代理,另一种是CGLib的动态代理,那么这两种有啥去别呢?
jdk动态代理
使用JDK动态代理的五大步骤:
- 通过实现InvocationHandler接口来自定义自己的InvocationHandler;
- 通过Proxy.getProxyClass获得动态代理类;
- 通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class);
- 通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入;
实现自己的InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
/**
* 构造方法
* @param target 目标对象
*/
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 执行目标对象的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在目标对象的方法执行之前简单的打印一下
System.out.println("------------------before------------------");
// 执行目标对象的方法
Object result = method.invoke(target, args);
// 在目标对象的方法执行之后简单的打印一下
System.out.println("-------------------after------------------");
return result;
}
/**
* 获取目标对象的代理对象
* @return 代理对象
*/
/* public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
} */
}
被代理对象的接口以及实现方法
public interface IHello {
void sayHello();
}
public class HelloImpl implements IHello {
@Override
public void sayHello() {
System.out.println("----------------Hello world!--------------");
}
}
测试类
/**
* 使用JDK动态代理的五大步骤:
* 1.通过实现InvocationHandler接口来自定义自己的InvocationHandler;
* 2.通过Proxy.getProxyClass获得动态代理类
* 3.通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)
* 4.通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入
* 5.通过代理对象调用目标方法
*/
public class MyProxyTest {
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
// =========================第一种==========================
// 1、生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 2、获取动态代理类
Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
// 3、获得代理类的构造函数,并传入参数类型InvocationHandler.class
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
// 4、通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
// 5、通过代理对象调用目标方法
iHello1.sayHello();
// ==========================第二种=============================
/**
* Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象,
*其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
*/
IHello iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器
new Class[]{IHello.class}, // 一组接口
new MyInvocationHandler(new HelloImpl())); // 自定义的InvocationHandler
iHello2.sayHello();
}
}
运行结果
------------------before------------------
----------------Hello world!--------------
-------------------after------------------
CGLib动态代理
CGLIB是通过继承实现代理,也就是生成一个类继承被代理类;所以当你的方法不能被继承时,该方法就不能被动态代理**(像final,private,protected这个比较特殊)**
实现一个业务类,注意,这个业务类并没有实现任何接口:
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
自定义MethodInterceptor:
/**
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
生成CGLIB代理对象调用目标方法:
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
运行结果:
HelloService构造
======插入前置通知======
HelloService:sayHello
======插入后者通知======