博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring事务深入剖析--spring事务失效的原因
阅读量:7060 次
发布时间:2019-06-28

本文共 12430 字,大约阅读时间需要 41 分钟。

 

 之前我们讲的分布式事务的调用都是在一个service中的事务方法,去调用另外一个service中的业务方法,

如果在一个sevice中存在两个分布式事务方法,在一个seivice中两个事务方法相互嵌套调用,对分布式事务有啥影响了

现在TestSevice中存在两个事务方法,funcA和FunctionB

现在有下面这样的一个需求

我们来看下具体的业务代码

package com.atguigu.spring.tx.xml.service.impl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.atguigu.spring.tx.BookShopService;import com.atguigu.spring.tx.xml.BookShopDao;import com.atguigu.spring.tx.xml.service.TestService;@Servicepublic class TestServiceInpl implements TestService {@Autowiredprivate BookShopDao bookShopDao;        public void setBookShopDao(BookShopDao bookShopDao) {        this.bookShopDao = bookShopDao;    }        @Transactional(propagation=Propagation.REQUIRES_NEW)    public void funB(){                bookShopDao.updateUserAccount("AA", 300);        throw new RuntimeException("funB is throw new RuntimeException ");    }    @Override    @Transactional    public void purchase(String username, String isbn) {        // TODO Auto-generated method stub        //想调用funbB方法        try{            funB();        }catch(Exception e){            System.out.println(e.getMessage().toString());        }                bookShopDao.updateUserAccount("AA", 100);    }}

 

purchase方法中使用了 @Transactional,然后在执行purchase的真正的业务方法执行调用了同一个类中的funB方法,funB方法使用了@Transactional(propagation=Propagation.REQUIRES_NEW)的注解 在funB方法中抛出了异常,在purchase方法中使用了try catch对异常进行捕获 在外买的方法中,我们编写一个测试类,调用purchase方法 applicationContext.xml
package com.atguigu.spring.tx.xml.service;public interface TestService {        public void purchase(String username, String isbn);    }
package com.atguigu.spring.tx.xml;public interface BookShopDao {    //�����Ż�ȡ��ĵ���    public int findBookPriceByIsbn(String isbn);        //������Ŀ��. ʹ��Ŷ�Ӧ�Ŀ�� - 1    public void updateBookStock(String isbn);        //�����û����˻����: ʹ username �� balance - price    public void updateUserAccount(String username, int price);}
package com.atguigu.spring.tx.xml;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Component;@Componentpublic class BookShopDaoImpl implements BookShopDao {    @Autowired    private JdbcTemplate jdbcTemplate;        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {        this.jdbcTemplate = jdbcTemplate;    }        @Override    public int findBookPriceByIsbn(String isbn) {        String sql = "SELECT price FROM book WHERE isbn = ?";        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);    }    @Override    public void updateBookStock(String isbn) {        //�����Ŀ���Ƿ��㹻, ������, ���׳��쳣        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);        if(stock == 0){            throw new BookStockException("��治��!");        }                String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";        jdbcTemplate.update(sql, isbn);    }    @Override    public void updateUserAccount(String username, int price) {        //��֤����Ƿ��㹻, ������, ���׳��쳣        String sql2 = "SELECT balance FROM account WHERE username = ?";        int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);        System.out.println("balance="+balance);                String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";        jdbcTemplate.update(sql, price, username);    }}

 

我们编写一个测试类,来编译pruchase方法,我们来看下运行结果
package com.atguigu.spring.tx.xml;import java.util.Arrays;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.atguigu.spring.tx.xml.service.BookShopService;import com.atguigu.spring.tx.xml.service.Cashier;import com.atguigu.spring.tx.xml.service.TestService;import com.atguigu.spring.tx.xml.service.impl.TestServiceInpl;public class CopyOfSpringTransactionTest2222 {    private ApplicationContext ctx = null;    private TestService testService = null;    private Cashier cashier = null;        {        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");        testService = ctx.getBean(TestService.class);    }        @Test    public void testBookShopService(){        testService.purchase("AA", "1001");    }    }

我们需要通过日志的信息来让spring框架默认底层帮助我们做了啥,spring框架默认使用commons-login框架

在阅读spring、springmvc源码的时候 会看到其中有很多代码中输出了日志信息  有时候这些信息对我们阅读源码、分析问题的时候有很大的作用,但是我们控制台并没有看到。那如何使这些日志信息显示出来呢?

解决:在pom.xml中加入 log4j 和commons-logging的依赖 然后在resources也就是src目录下下添加log4j.properties文件

---------------------

log4j.properties

log4j.rootLogger=DEBUG, Console    #Console  log4j.appender.Console=org.apache.log4j.ConsoleAppender  log4j.appender.Console.layout=org.apache.log4j.PatternLayout  log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n    log4j.logger.java.sql.ResultSet=INFO  log4j.logger.org.apache=INFO  log4j.logger.java.sql.Connection=DEBUG  log4j.logger.java.sql.Statement=DEBUG  log4j.logger.java.sql.PreparedStatement=DEBUG

这样在项目启动的时候就可以打印spring框架的日志了

 我们通过日志信息来查看事务的执行情况

Initializing transaction synchronization Getting transaction for com.atguigu.spring.tx.xml.service.impl.TestServiceInpl.purchase]=======purchase start====== =======funB start====== Executing prepared SQL queryl.TestServiceInpl] - =======purchase end====== Triggering beforeCommit synchronizationInitiating transaction commitCommitting JDBC transaction on Connection [co

 

 通过日志我们可以看出,在调用purchase方法的时候开启了一个新的事情,然后调用purchase方法,在调用funB方法的时候,并没有开启一个新的事务,而是直接使用之前创建的

事务 @Transactional(propagation=Propagation.REQUIRES_NEW)这个事务实现了,上面调用funB的方法可以简写成下面的形式

@Override    @Transactional    public void purchase(String username, String isbn) {        // TODO Auto-generated method stub        log.debug("=======purchase start======");        //想调用funbB方法        try{            log.debug("=======funB start======");            bookShopDao.updateUserAccount("AA", 300);            throw new RuntimeException("funB is throw new RuntimeException ");        }catch(Exception e){            System.out.println(e.getMessage().toString());        }                bookShopDao.updateUserAccount("AA", 100);        log.debug("=======purchase end======");    }

 

所以执行的时候:  bookShopDao.updateUserAccount("AA", 300);  bookShopDao.updateUserAccount("AA", 100); 都会提交到数据库中,bookShopDao.updateUserAccount("AA", 300)不会因为抛出异常而回滚因为异常被try  catch出来掉了,所以看日志信息是一种很正常的定位问题的一种好的习惯 上面为啥@Transactional(propagation=Propagation.REQUIRES_NEW)这个事务会失效了,接下来我们进行讲解 我们再讲事务失效之前,我们在来看这样的一种常见
@Override    @Transactional    public void purchase(String username, String isbn) {        // TODO Auto-generated method stub        log.debug("=======purchase start======");        //想调用funbB方法        try{            log.debug("=======funB start======");            bookShopDao.updateUserAccount("AA", 300);            throw new RuntimeException("funB is throw new RuntimeException ");        }catch(Exception e){            System.out.println(e.getMessage().toString());           //把异常跑出去            throw e;        }                bookShopDao.updateUserAccount("AA", 100);        log.debug("=======purchase end======");    }

 

上面我们把异常跑出去,异常会被spring事务的aop框架拦截到异常,对异常进行处理,导致整个purchase方法中的数据库操作都会回滚

bookShopDao.updateUserAccount("AA", 300);

bookShopDao.updateUserAccount("AA", 100);

都会失败

接下来我们讲解@Transactional(propagation=Propagation.REQUIRES_NEW)这个事务为啥会失效了

根本的原因在于jdk的动态代理导致的事务的失效

我们先编写一个动态代理

package com.atguigu.spring.tx.xml.service.impl;public interface DemoService {        public void test();    public void test1();    }
package com.atguigu.spring.tx.xml.service.impl;public class DemoServiceImpl implements DemoService {    @Override    public void test() {        // TODO Auto-generated method stub     System.out.println("test is called");    }    @Override    public void test1() {        // TODO Auto-generated method stub        System.out.println("test1 is called");    }}

 

接下来我们通过jdk的动态代理的方式来生成一个DemoServiceImpl 对象
 
package com.atguigu.spring.tx.xml.service.impl;import java.lang.reflect.Method;import org.springframework.cglib.proxy.InvocationHandler;import org.springframework.cglib.proxy.Proxy;public class MyHandler implements InvocationHandler {    //目标对象    private Object target;            public MyHandler(Object demoService) {        this.target = demoService;    }    @Override    public Object invoke(Object arg0, Method method, Object[] args)            throws Throwable {        // 做另外的业务处理        if(method.getName().contains("test")){            System.out.println("====加入了其他处理===");        }        return method.invoke(target, args);    }        public  static void main(String[] args){        MyHandler myHandler = new MyHandler(new DemoServiceImpl());        DemoService proxyInstance = (DemoService) Proxy.newProxyInstance(MyHandler.class.getClassLoader(), new Class[]{DemoService.class}, myHandler);        proxyInstance.test();        proxyInstance.test1();            }}
 

 

 

 

程序运行的输出结果为

====加入了其他处理===

test is called
====加入了其他处理===
test1 is called

 

现在上面中purahase调用了funB方法,等价于在test方法中调用了test1方法
package com.atguigu.spring.tx.xml.service.impl;public class DemoServiceImpl implements DemoService {    @Override    public void test() {        // TODO Auto-generated method stub        this.test1();     System.out.println("test is called");    }    @Override    public void test1() {        // TODO Auto-generated method stub        System.out.println("test1 is called");    }}

 

我们运行看下日志的打印
package com.atguigu.spring.tx.xml.service.impl;import java.lang.reflect.Method;import org.springframework.cglib.proxy.InvocationHandler;import org.springframework.cglib.proxy.Proxy;public class MyHandler implements InvocationHandler {    //目标对象    private Object target;            public MyHandler(Object demoService) {        this.target = demoService;    }    @Override    public Object invoke(Object arg0, Method method, Object[] args)            throws Throwable {        // 做另外的业务处理        if(method.getName().contains("test")){            System.out.println("====加入了其他处理===");        }        return method.invoke(target, args);    }        public  static void main(String[] args){        MyHandler myHandler = new MyHandler(new DemoServiceImpl());        DemoService proxyInstance = (DemoService) Proxy.newProxyInstance(MyHandler.class.getClassLoader(), new Class[]{DemoService.class}, myHandler);        proxyInstance.test();            }}

====加入了其他处理===

test1 is called
test is called

通过日志我们可以看出,在执行test方法的时候被jdk代理了,执行this.test1()方法的时候没有被jdk的动态代理所拦截

所以打印日志仅仅输出 了一条test1 is called

这里我们要一定主要下面的两点:

1、test()方法是被jdk动态产生的代理对象所调用的;所以打印出来了====加入了其他处理===日志信息

2、test1()方法不是被jdk的动态代理对象调用的,而是被真实的new DemoServiceImpl()出来的对象所调用的

上面我们加日志信息进行调试,可以看出来

DemoService proxyInstance = (DemoService) Proxy.newProxyInstance(MyHandler.class.getClassLoader(), new Class[]{DemoService.class}, myHandler);

proxyInstance.test();

proxyInstance就是代理对象调用了test方法

我们在test方法中加入日志信息。看当前的对象是

package com.atguigu.spring.tx.xml.service.impl;public class DemoServiceImpl implements DemoService {    @Override    public void test() {        // TODO Auto-generated method stub        //打印出当前对象的信息        System.out.println(this.getClass().getName());        this.test1();     System.out.println("test is called");    }    @Override    public void test1() {        // TODO Auto-generated method stub        System.out.println("test1 is called");    }}

 

打印的日志为:

====加入了其他处理===

com.atguigu.spring.tx.xml.service.impl.DemoServiceImpl
test1 is called
test is called

可以看出调用test1方法的是当前的this对象,就是com.atguigu.spring.tx.xml.service.impl.DemoServiceImpl,就是而是被真实的new DemoServiceImpl()出来的对象所调用的

没有被真实的代理对象调用,导致test1上面的注解的事务失效

spring的事务是通过jdbc的动态代理实现的,只有被代理对象调用的方法上面的事务才有效,只有被代理对象直接调用的方法上面的事务才有效果

现在如果要让上面的funB的事务生效,如果是代理对象来proxy对象直接调用funB方法就可以让funB的事务生效

 

 

 

转载于:https://www.cnblogs.com/kebibuluan/p/10733094.html

你可能感兴趣的文章
自动化运维工具Saltstack学习笔记(一)
查看>>
Linux:chattr命令和chgrp命令
查看>>
Docker技术这些应用场景,你知道吗?
查看>>
“码农”的逆袭
查看>>
VMware无法与物理机连通Could not connect Ethernet0 to virtual network "VMnet8"完美解决
查看>>
如何利用软文让你的产品广告上百度首页
查看>>
我也来说说“自学IT能走多远”
查看>>
如何通过今日头条引精准流量,学完即用
查看>>
软件项目经理新手上路13 - 给新手的建议
查看>>
简单聊聊Qos—优先级队列 PQ
查看>>
Exchange 2013部署系列之(十一)Office Web Apps部署
查看>>
[转载] 2011 ScrumGathering大会简要记录 - 周金根
查看>>
OpenSNS系统评测:社群经济的第一核心是身份标签
查看>>
Linux C语言程序连接mysql数据库
查看>>
Fortigate防火墙配置***使用Radius认证配置说明
查看>>
iOS开发那些事-响应内存警告
查看>>
六、CPU优化(3)处理器组
查看>>
海尔布局厨电产业 国际化与场景化成新方向
查看>>
SFB 项目经验-04-共存迁移-Lync 2013-SFB 2015-Godaddy-更新公网证书
查看>>
Linq to SharePoint与权限提升
查看>>