zoukankan      html  css  js  c++  java
  • 关于Spring事务的原理,以及在事务内开启线程,连接池耗尽问题.

    主要以结果为导向解释Spring 事务原理,连接池的消耗,以及事务内开启事务线程要注意的问题.

    Spring 事务原理这里不多说,网上一搜一大堆,也就是基于AOP配合ThreadLocal实现.

    这里强调一下Spring Aop 以及Spring 注解式注入在非Spring容器管理的类中是无效的.

    因为Spring Aop是在运行时实现字节码增强,字节码增强有多种实现方法,请自行了解,原生AspectJ是编译时织入,但是需要特定的编译器.语法并没有Spring Aop好理解.

     

    先看下Spring 事务传播行为类型

     

    事务传播行为类型

    说明

    PROPAGATION_REQUIRED

    如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。

    PROPAGATION_SUPPORTS

    支持当前事务,如果当前没有事务,就以非事务方式执行。

    PROPAGATION_MANDATORY

    使用当前的事务,如果当前没有事务,就抛出异常。

    PROPAGATION_REQUIRES_NEW

    新建事务,如果当前存在事务,把当前事务挂起。

    PROPAGATION_NOT_SUPPORTED

    以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    PROPAGATION_NEVER

    以非事务方式执行,如果当前存在事务,则抛出异常。

    PROPAGATION_NESTED

    如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED类似的操作。

     

    打开日记debug模式,留意控制台输出

    以下测试为了可读性以及更容易理解全是基于Spring注解式事务,而没有配置声明式事务.

    测试1:

    可以看见只创建了一个SqlSession以及一个事务,在方法内所有操作都使用同一个连接,同一个事务

    @RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
        @Transactional(propagation = Propagation.REQUIRED)
        public void testThreadTx(){
    //此方法没有事务(当前方法是 Propagation.REQUIRED)
                Quotation quotation = quotationService.findEntityById(new String("1"));
    //此方法没有事务(当前方法是 Propagation.REQUIRED)
                quotationService.updateEntity(quotation);
        }

    //查看控制台输出(红字关键部分,第三个查询是更新方法内部需要先查询一次再更新,可以无视)

    Creating a new SqlSession
    Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
    ansaction(line/:54) -JDBC Connection [1068277098(com.mysql.jdbc.JDBC4Connection@5d92bace)] will be managed by Spring
    otationMapper.findEntityById(line/:54) -==>  Preparing: SELECT * FROM table WHERE id = 1 
    otationMapper.findEntityById(line/:54) -==> Parameters: 
    otationMapper.findEntityById(line/:54) -<==      Total: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
    Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4] from current transaction
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
    Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4] from current transaction
    otationMapper.updateEntity(line/:54) -==>  Preparing: update ….. where id = 1 
    otationMapper.updateEntity(line/:54) -==> Parameters: 
    otationMapper.updateEntity(line/:54) -<==    Updates: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
    .impl.UserOperationLogServiceImpl(line/:41) -请求所用时间:207
    .impl.UserOperationLogServiceImpl(line/:42) -请求结束*******************************************************************************
    Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
    Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
    Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]

    测试2:不使用事务

    可以看出在非事务操作数据库,会使用多个连接,非常不环保,这里给稍微多线程插入埋下一个陷阱

    @RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
    //    @Transactional(propagation = Propagation.REQUIRED)
        public void testThreadTx(){
                Quotation quotation = quotationService.findEntityById(new String("1"));
                quotationService.updateEntity(quotation);
        }
    //查看控制台输出(红字关键部分,第三个查询是更新方法内部需要先查询一次再更新,可以无视)
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f7b94f] was not registered for synchronization because synchronization 
    ansaction(line/:54) -JDBC Connection [352410768(com.mysql.jdbc.JDBC4Connection@c63fcb6)] will not be managed by Spring
    otationMapper.findEntityById(line/:54) -==>  Preparing: SELECT * FROM table WHERE id = 1 
    otationMapper.findEntityById(line/:54) -==> Parameters: 
    otationMapper.findEntityById(line/:54) -<==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f7b94f]
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a41785a] was not registered for synchronization because synchronization 
    ansaction(line/:54) -JDBC Connection [1615108970(com.mysql.jdbc.JDBC4Connection@38377d86)] will not be managed by Spring
    otationMapper.findEntityById(line/:54) -==>  Preparing: SELECT * FROM table WHERE id = 1 
    otationMapper.findEntityById(line/:54) -==> Parameters: 
    otationMapper.findEntityById(line/:54) -<==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a41785a]
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@181e5a22] was not registered for synchronization because synchronization 
    ansaction(line/:54) -JDBC Connection [2096339748(com.mysql.jdbc.JDBC4Connection@5d4e9892)] will not be managed by Spring
    otationMapper.updateEntity(line/:54) -==>  Preparing: update …. where id = 1 
    otationMapper.updateEntity(line/:54) -==> Parameters: 
    otationMapper.updateEntity(line/:54) -<==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@181e5a22]
    .impl.UserOperationLogServiceImpl(line/:41) -请求所用时间:614
    .impl.UserOperationLogServiceImpl(line/:42) -请求结束*******************************************************************************

    测试3:

    @RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
        @Transactional(propagation = Propagation.REQUIRED)
        public void testThreadTx(){
                final ExecutorService executorService = Executors.newFixedThreadPool(3);
                Quotation quotation = quotationService.findEntityById(new String("1"));
                quotationService.updateEntity(quotation);
                List<Future<Integer>> futures = new ArrayList<Future<Integer>>(3);
                for(int i=0;i<3;i++){
                        Callable<Integer> task = new Callable<Integer>() {
                            @Override
                            public Integer call() throws Exception {
                                Quotation quotation = quotationService.findEntityById(new String("1"));
                                quotationService.updateEntity(quotation);
                                return null;
                            }
                        };
                        futures.add(executorService.submit(task));
                }
                executorService.shutdown();
        }

    //查看控制台输出(红字关键部分,第三个查询是更新方法内部需要先查询一次再更新,可以无视)

    为了节篇幅,这里不贴出控制台数据

    大概就是输出了10个Creating a new SqlSession(大概有些同学使用了多线程,把线程池耗完了也没弄明白原因)

    外层方法启动一个,内部3个线程,每个线程3个.一共是使用了10个连接.

    为什么?这涉及到ThreadLocal以及线程私有栈的概念.如果Spring 事务使用InhertableThreadLocal就可以把连接传到子线程,但是为什么Spring不那么干呢?因为这样毫无意义,如果把同一个连接传到子线程,那就是SQL操作会串行执行,那何必还多线程呢?

    有关于ThreadLocal,InhertableThreadLocal配合线程池的一些陷阱

    请看我另一篇文章:

    ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑

    测试4:

    既然使用同一个事务,不能实现并发操作,那么只能折中,在每一个线程开启一个事务,减少创建更多的连接,执行完毕以后可以返回操作成功失败结果,反馈给用户

    @RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
    //    @Transactional(propagation = Propagation.REQUIRED)
        public void testThreadTx(){
                ExecutorService executorService = Executors.newFixedThreadPool(3);
                List<Future<Integer>> futures = new ArrayList<Future<Integer>>(3);
                for(int i=0;i<3;i++){
                        Callable<Integer> task = new Callable<Integer>() {
                            @Override
                            public Integer call() throws Exception {
                                quotationService.doSomeThing();
                                return null;
                            }
                        };
                        futures.add(executorService.submit(task));
                }
                executorService.shutdown();
        }
    
        //封装一下
        @Override
        @Transactional(propagation =Propagation.REQUIRED)
        public void doSomeThing(){
            Quotation quotation = this.findEntityById(new String("1"));
            this.updateEntity(quotation);
        }
    //查看控制台输出,只会创建3个连接,为节省篇幅,这里不贴出控制台所有数据
    Creating a new SqlSession
    Creating a new SqlSession
    Creating a new SqlSession

    最后小技巧PROPAGATION_NOT_SUPPORTED(仅仅为了让Spring能获取ThreadLocal的connection),如果不使用事务,但是同一个方法多个对数据库操作,那么使用这个传播行为可以减少消耗数据库连接

        @RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public void testThreadTx(){
            Quotation quotation = quotationService.findEntityById(new String("1"));
            quotation.setStatus(ClassDataManager.STATE_N);
            quotationService.updateEntity(quotation);
        }
    //这样只会创建一个SqlSession
  • 相关阅读:
    2.MYSQL之初体验
    nginx+uWSGI+django+virtualenv+supervisor
    静态动态网页
    web server 基础知识
    nginx与location语法详解
    编译安装nginx
    虚拟环境之virtualenvwrapper
    python开发之virtualenv
    【剑指Offer】面试题12. 矩阵中的路径
    【剑指Offer】面试题11. 旋转数组的最小数字
  • 原文地址:https://www.cnblogs.com/sweetchildomine/p/6591692.html
Copyright © 2011-2022 走看看