特定需求下动态代理导致的Spring事务不能回滚
2017-10-19 22:08
423 查看
我们先来设定一下需求场景,我们首先设定两个事务,事务parent和事务child,首先我们同时提交两个事务:
前端调用代码:
[java] view
plain copy
print?
private TestInterface orgiInterface;
public Proxytest1(Testinterface test){
this.orgiInterface=test;
}
public TestInterface createProxy(){
return (TestInterface)Proxy.newProxyInstance(ProxyTest1.class.getClassLoader(),new Class[]{TestInterface.class},this);
}
@Override
public object invoke(Object proxy,Method method,objec[] args){
if(method.getName().startsWith("test")){
System.out.println("========这里是华丽的分隔符=============");//我们使用这个标志来识别是否使用代理还是使用方法本体
}
return method.invoke(orginInterface,args);
}
public static void main(String[] args) throws Exception(){
TestInterFaceImpl t=new TestInterFaceImpl();
Proxytest1 proxyTest=new ProxyTest1(t);
TestInterface proxy=proxyTest.createProxy();
proxy.test();
proxy.test2();
}
Proxy代理:
[java] view
plain copy
print?
public interface TestInterface{
public void test();
public void test2();
}
代理实现类:
[java] view
plain copy
print?
public classs menuInfoServiceTest{
@AutoWired
private menuInfoService service;
@Test
public void test() throws Exception{
service.parentTransaction();
}
@Test
public void test2() throws Exception{
service.childTransaction();
}
}
事务本体:
[java] view
plain copy
print?
private Logger logger=LoggerFactory.getLogger(getClass());//在这里,我们是用log4j来记录日志
//这里是两个正常的事务,其中parent正常提交,child抛出异常,进行回滚
@Transactional
public void parentTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("parent");
menuInfo.setName("Davie parent");
menuInfoDao.insertSelective(menuInfo);
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void childTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("child");
menuInfo.setName("Davie child");
menuInfoDao.insertSelective(menuInfo);
throw new RuntimeException("child Exception....................");
}
执行,一下,效果正常,child事务回滚,parent事务提交,数据插入了parent提交的数据。控制台打印出:
========这里是华丽的分隔符=============
========这里是华丽的分隔符=============
变故:
现在万恶的需求人员来了,他们要求,在事务parent提交之前,先提交child事务,而且,效果为,parent正常提交,child事务回滚,如果是我们刚开始接触这块,我想大部分人会这么写:
[java] view
plain copy
print?
//方式一:
@Transactional
public void parentTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("parent");
menuInfo.setName("Davie parent");
menuInfoDao.insertSelective(menuInfo);
try{
childTransaction();
}catch(Exception ex){
logger.info("parent catch child exception",ex);
}
}
@Transactional(propagation=Propagetion.REQUITES_NEW)
public void childTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("child");
menuInfo.setName("Davie child");
menuInfoDao.insertSelective(menuInfo);
throw new RuntimeException("child Exception....................");
}
或者是:
[java] view
plain copy
print?
//方式二:
@Transactional
public void parentTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("parent");
menuInfo.setName("Davie parent");
menuInfoDao.insertSelective(menuInfo);
childTransaction();
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void childTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("child");
menuInfo.setName("Davie child");
menuInfoDao.insertSelective(menuInfo);
throw new RuntimeException("child Exception....................");
}
但是,这都是不对的,我们先来说一下修改效果大家就明白了(要求:parent提交,child回滚):
方式一:控制台只打印出一条“========这里是华丽的分隔符=============”,然后数据插入了parent,同时也插入了child数据。
方式二:控制台也只打印出一条“========这里是华丽的分隔符=============”,但是数据库没有插如数据。
这是为什么呢?
首先我们来说一下为什么这两种方式控制台都只打印出一条标志语句,相信大家也能猜到,我们的标志语句是用来干嘛的,是用来标记是否生成使用代理的。那这种现象说明了什么呢?说明这两个事务里面,有一个并没有使用动态生成的代理,而是直接调用的方法本体,也就是内部方法,显而易见,就是child方法。我们总结一下,就是在使用jdk里面的动态代理时,像这种事务内部调用已声明事务的方法是没有用的,这种方式下,child事务被覆盖,说得严重一点,就是child在这种方式下,根本就不具备事务性。相当于直接调用内部方法,本地服务,是没有产生代理的,而是使用的方法本体。这也就导致我声明事务,抛出异常却不能回滚的一个重要原因,而很多人还在想我一样,埋头苦想到底是哪里出了问题。
针对于方式一和方式二读数据库操作的不同,其实也很明了,方式一中,child抛出的一场被catch吃掉了,没有抛出,导致parent事务正常提交,所以插入了两条数据。而方式二中,child抛出的RuntimeException异常抛到了parent中,被发现,导致parent事务回滚,所以一条数据都没有插进去,是不是很简单。
那我们如何解决这种问题呢?在使用jdk动态代理的时候,我们不可以使用以上这种事务嵌套的方式来解决问题,最简单的方法就是把问题提到service或者是更靠前的逻辑中去解决,使用service.xxxtransaction是不会出现这种问题的。
以上只是自己在实践中遇到的一点问题,分享出来,供大家参考。
前端调用代码:
[java] view
plain copy
print?
private TestInterface orgiInterface;
public Proxytest1(Testinterface test){
this.orgiInterface=test;
}
public TestInterface createProxy(){
return (TestInterface)Proxy.newProxyInstance(ProxyTest1.class.getClassLoader(),new Class[]{TestInterface.class},this);
}
@Override
public object invoke(Object proxy,Method method,objec[] args){
if(method.getName().startsWith("test")){
System.out.println("========这里是华丽的分隔符=============");//我们使用这个标志来识别是否使用代理还是使用方法本体
}
return method.invoke(orginInterface,args);
}
public static void main(String[] args) throws Exception(){
TestInterFaceImpl t=new TestInterFaceImpl();
Proxytest1 proxyTest=new ProxyTest1(t);
TestInterface proxy=proxyTest.createProxy();
proxy.test();
proxy.test2();
}
Proxy代理:
[java] view
plain copy
print?
public interface TestInterface{
public void test();
public void test2();
}
代理实现类:
[java] view
plain copy
print?
public classs menuInfoServiceTest{
@AutoWired
private menuInfoService service;
@Test
public void test() throws Exception{
service.parentTransaction();
}
@Test
public void test2() throws Exception{
service.childTransaction();
}
}
事务本体:
[java] view
plain copy
print?
private Logger logger=LoggerFactory.getLogger(getClass());//在这里,我们是用log4j来记录日志
//这里是两个正常的事务,其中parent正常提交,child抛出异常,进行回滚
@Transactional
public void parentTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("parent");
menuInfo.setName("Davie parent");
menuInfoDao.insertSelective(menuInfo);
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void childTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("child");
menuInfo.setName("Davie child");
menuInfoDao.insertSelective(menuInfo);
throw new RuntimeException("child Exception....................");
}
执行,一下,效果正常,child事务回滚,parent事务提交,数据插入了parent提交的数据。控制台打印出:
========这里是华丽的分隔符=============
========这里是华丽的分隔符=============
变故:
现在万恶的需求人员来了,他们要求,在事务parent提交之前,先提交child事务,而且,效果为,parent正常提交,child事务回滚,如果是我们刚开始接触这块,我想大部分人会这么写:
[java] view
plain copy
print?
//方式一:
@Transactional
public void parentTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("parent");
menuInfo.setName("Davie parent");
menuInfoDao.insertSelective(menuInfo);
try{
childTransaction();
}catch(Exception ex){
logger.info("parent catch child exception",ex);
}
}
@Transactional(propagation=Propagetion.REQUITES_NEW)
public void childTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("child");
menuInfo.setName("Davie child");
menuInfoDao.insertSelective(menuInfo);
throw new RuntimeException("child Exception....................");
}
或者是:
[java] view
plain copy
print?
//方式二:
@Transactional
public void parentTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("parent");
menuInfo.setName("Davie parent");
menuInfoDao.insertSelective(menuInfo);
childTransaction();
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void childTransaction(){
MenuInfo menuInfo=new MenuInfo();
menuInfo.setMemo("child");
menuInfo.setName("Davie child");
menuInfoDao.insertSelective(menuInfo);
throw new RuntimeException("child Exception....................");
}
但是,这都是不对的,我们先来说一下修改效果大家就明白了(要求:parent提交,child回滚):
方式一:控制台只打印出一条“========这里是华丽的分隔符=============”,然后数据插入了parent,同时也插入了child数据。
方式二:控制台也只打印出一条“========这里是华丽的分隔符=============”,但是数据库没有插如数据。
这是为什么呢?
首先我们来说一下为什么这两种方式控制台都只打印出一条标志语句,相信大家也能猜到,我们的标志语句是用来干嘛的,是用来标记是否生成使用代理的。那这种现象说明了什么呢?说明这两个事务里面,有一个并没有使用动态生成的代理,而是直接调用的方法本体,也就是内部方法,显而易见,就是child方法。我们总结一下,就是在使用jdk里面的动态代理时,像这种事务内部调用已声明事务的方法是没有用的,这种方式下,child事务被覆盖,说得严重一点,就是child在这种方式下,根本就不具备事务性。相当于直接调用内部方法,本地服务,是没有产生代理的,而是使用的方法本体。这也就导致我声明事务,抛出异常却不能回滚的一个重要原因,而很多人还在想我一样,埋头苦想到底是哪里出了问题。
针对于方式一和方式二读数据库操作的不同,其实也很明了,方式一中,child抛出的一场被catch吃掉了,没有抛出,导致parent事务正常提交,所以插入了两条数据。而方式二中,child抛出的RuntimeException异常抛到了parent中,被发现,导致parent事务回滚,所以一条数据都没有插进去,是不是很简单。
那我们如何解决这种问题呢?在使用jdk动态代理的时候,我们不可以使用以上这种事务嵌套的方式来解决问题,最简单的方法就是把问题提到service或者是更靠前的逻辑中去解决,使用service.xxxtransaction是不会出现这种问题的。
以上只是自己在实践中遇到的一点问题,分享出来,供大家参考。
相关文章推荐
- 特定需求下动态代理导致的Spring事务不能回滚
- Spring源码分析--@Autowired注入的不是代理对象,导致事务回滚失败(@Transactional无效)
- spring代理iBATIS时事务不能回滚
- jdk动态代理引起的spring事务不起作用
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
- Spring事务管理机制的实现原理-动态代理
- spring整合Hibernate事务不能自动回滚
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
- Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。
- 面试必备技能:JDK动态代理给Spring事务埋下的坑!
- 分析动态代理给Spring事务埋下的坑 推荐
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别
- 面试必备技能:JDK动态代理给Spring事务埋下的坑!
- 面试必备技能:JDK动态代理给Spring事务埋下的坑!
- spring事务不能回滚 异常捕获不抛出
- JDK动态代理给Spring事务埋下的坑
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
- jdk动态代理引起的spring事务不起作用
- JDK动态代理给Spring事务埋下的坑