• Mybatis BatchExecutor源码分析


    在介绍Mybatis批处理之前,首先回顾一下PreparedStatement的批处理

      假如现在需要向用户表中插入两条数据,如果不适用批处理的实现如下,有三次网络交互
    //加载驱动
        Class.forName("org.hsqldb.jdbcDriver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:batch_keys", "sa", "");
        //设置不自动提交
        connection.setAutoCommit(false);
        //创建prepareStatement,存在一次网络交互,预存储statement
        PreparedStatement preparedStatement=connection.prepareStatement("insert into users values(?,?) ");
        //设置参数
        preparedStatement.setInt(1, 1);
        preparedStatement.setString(2, "Cujo");
        //执行sql 网络交互一次
        preparedStatement.executeUpdate();
    
        preparedStatement.setInt(1, 2);
        preparedStatement.setString(2, "zhangsan");
        preparedStatement.addBatch();
        //执行sql 网络交互一次
        preparedStatement.executeUpdate();
    

      

       为了减少网络交互次数可以使用批处理的方式实现,代码如下:
    //加载驱动
        Class.forName("org.hsqldb.jdbcDriver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:batch_keys", "sa", "");
        //设置不自动提交
        connection.setAutoCommit(false);
        //创建prepareStatement,存在一次网络交互,预存储statement
        PreparedStatement preparedStatement=connection.prepareStatement("insert into users values(?,?) ");
        //设置参数
        preparedStatement.setInt(1, 1);
        preparedStatement.setString(2, "Cujo");
        //添加到批处理
        preparedStatement.addBatch();
    
        preparedStatement.setInt(1, 2);
        preparedStatement.setString(2, "zhangsan");
        preparedStatement.addBatch();
        //执行批处理,该处将添加到批处理中的sql都执行完
        preparedStatement.executeBatch();
    

      

     上文代码中可以看到可以有效减少网络交互次数,如果插入100条记录,就可以减少99次网络交互;这样效果就比较明显了。下面看一下Mybatis中的批处理是如何处理的。

     

    主要的处理过程:
    1. 首先指定ExecutorType.BATCH打开sqlSession
    2. 多长重复执行单挑SQL
    3. 执行结束后调用flushStatements()执行批处理
    @Override
      public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
        //获取配置信息
        final Configuration configuration = ms.getConfiguration();
        //创建StatementHandler
        final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
        //获取BoundSql
        final BoundSql boundSql = handler.getBoundSql();
        //从boundSql中获取 sql
        final String sql = boundSql.getSql();
        final Statement stmt;
        //如果sql等于currentSql同时MappedStatement与currentStatement相同, 就是同一条SQL,但是参数可能不同,这样就不需要重复创建PrepareStatement
        //可以减少网络交互次次数,通过源码可以发现批处理中最佳时间就是同样的sql要一起执行,不要存在不同sql间隔这样的场景出现
        if (sql.equals(currentSql) && ms.equals(currentStatement)) {
          int last = statementList.size() - 1;
          //获取最后一次创建statement
          stmt = statementList.get(last);
          //设置事务超时时间
          applyTransactionTimeout(stmt);
          //设置stmt参数
          handler.parameterize(stmt);
          //获取对应的批量结果
          BatchResult batchResult = batchResultList.get(last);
          //将参数对象添加到参数列表中
          batchResult.addParameterObject(parameterObject);
        } else {//和上一次创建的SQL不同,则需要重新创建PrepareStatement
          Connection connection = getConnection(ms.getStatementLog());
          stmt = handler.prepare(connection, transaction.getTimeout());
          handler.parameterize(stmt);  
          currentSql = sql;
          currentStatement = ms;
          statementList.add(stmt);
          batchResultList.add(new BatchResult(ms, sql, parameterObject));
        }
        //添加到批处理
        handler.batch(stmt);
        //返回默认值
        return BATCH_UPDATE_RETURN_VALUE;
      }
    
    @Override
      public List doFlushStatements(boolean isRollback) throws SQLException {
        try {
          List results = new ArrayList();
          if (isRollback) {
            return Collections.emptyList();
          }
          //遍历所有satement
          for (int i = 0, n = statementList.size(); i < n; i++) {
            Statement stmt = statementList.get(i);
            applyTransactionTimeout(stmt);
            //获取对应的结果对象
            BatchResult batchResult = batchResultList.get(i);
            try {
              //stmt.executeBatch执行批处理,并将更新条数保存到执行结果中;
              batchResult.setUpdateCounts(stmt.executeBatch());
              //获取结果对应到mappedStatement
              MappedStatement ms = batchResult.getMappedStatement();
              //获取参数列表
              List
    
    
      @Override
      public  List doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException {
        Statement stmt = null;
        try {
          //刷新
          flushStatements();
          //获取配置
          Configuration configuration = ms.getConfiguration();
          //创建StatementHandler,该处可以通过插件增强
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
          //获取连接
          Connection connection = getConnection(ms.getStatementLog());
          //预处理
          stmt = handler.prepare(connection, transaction.getTimeout());
          //设置参数
          handler.parameterize(stmt);
          //执行了SQL 也就是说查询是没有批处理的
          return handler.query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    

      

    通过上文中的代码可以发现,在使用批处理的时候有几点需要注意:
     
    1. 在批处理过程中不要有查询SQL,如果有需要使用不同的executor
    2. 在批量处理过程中,相同的SQL要一起处理,不要有不同SQL间隔添加到批处理中
    3. 在大量的执行过程中可以部分执行flush,最后执行事务提交
  • 相关阅读:
    ansible for devops 读书笔记第二章Ad-Hoc Commands
    ansible for devops读书笔记第一章
    python3 获取天气
    简单cpu web flask mysql
    mysql mysqldump只导出表结构或只导出数据的实现方法
    nginx 限制solr
    [Selenium] 如何使 InternetExplorerDriver 每次启动的端口不会随机变化
    [Selenium] 如何绕过 IE 的安全模式
    [Selenium] close alert window
    [Selenium] waitUntilAllAjaxRequestCompletes
  • 原文地址:https://www.cnblogs.com/wei-zw/p/8967942.html
Copyright © 2020-2023  润新知