• Spring事务深入剖析--spring事务失效的原因


     之前我们讲的分布式事务的调用都是在一个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;
    
    @Service
    public class TestServiceInpl implements TestService {
    
    @Autowired
    private 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
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
        
        <context:component-scan base-package="com.atguigu.spring"></context:component-scan>
        
        <!-- 导入资源文件 -->
        <context:property-placeholder location="classpath:db.properties"/>
        
        <!-- 配置 C3P0 数据源 -->
        <bean id="dataSource"
            class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="user" value="${jdbc.user}"></property>
            <property name="password" value="${jdbc.password}"></property>
            <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
            <property name="driverClass" value="${jdbc.driverClass}"></property>
    
            <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
            <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
        </bean>
        
        <!-- 配置 Spirng 的 JdbcTemplate -->
        <bean id="jdbcTemplate" 
            class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
        <!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 -->
        <bean id="namedParameterJdbcTemplate"
            class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
            <constructor-arg ref="dataSource"></constructor-arg>    
        </bean> 
        
        <!-- 配置事务管理器 -->
        <bean id="transactionManager" 
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
        <!-- 启用事务注解 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
        
    </beans>
    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;
    
    @Component
    public 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 query
    l.TestServiceInpl] - =======purchase end======
     Triggering beforeCommit synchronization
    Initiating transaction commit
    Committing 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的事务生效


     
  • 相关阅读:
    Fluentd部署:如何监控Fluentd
    【615】国内国外经纬度坐标转换
    【614】矢量数据转栅格数据(cv2.fillPoly/cv2.polylines)
    【613】U-Net 相关
    【612】深度学习模型相关问题
    【611】keras 后端 backend 相关函数(Dice实现)
    面试官:new 关键字在 JVM 中是如何执行的?
    IntelliJ IDEA 2021.2 发布,这次要干掉 FindBugs 了!!
    Nginx 实现 10w+ 并发之 Linux 内核优化
    我们真的需要全栈开发吗?
  • 原文地址:https://www.cnblogs.com/kebibuluan/p/10733094.html
Copyright © 2020-2023  润新知