特定需求下动态代理导致的Spring事务不能回滚
来源:互联网 发布:程序员软件工程师 编辑:程序博客网 时间:2024/05/22 01:54
我们先来设定一下需求场景,我们首先设定两个事务,事务parent和事务child,首先我们同时提交两个事务:
前端调用代码:
- 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代理:
- public interface TestInterface{
-
- public void test();
- public void test2();
-
- }
代理实现类:
- public classs menuInfoServiceTest{
- @AutoWired
- private menuInfoService service;
-
- @Test
- public void test() throws Exception{
- service.parentTransaction();
- }
-
- @Test
- public void test2() throws Exception{
- service.childTransaction();
- }
- }
事务本体:
- private Logger logger=LoggerFactory.getLogger(getClass());
-
-
- @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事务回滚,如果是我们刚开始接触这块,我想大部分人会这么写:
-
- @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....................");
- }
或者是:
-
- @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是不会出现这种问题的。
以上只是自己在实践中遇到的一点问题,分享出来,供大家参考。