与Spring整合的Mybatis没法真正使用BatchExecutor

  标题可能有点懵人,我要表达的意思就是在和Spring整合后,Mybatis的确能够使用到BatchExecutor,但是看现象不是批量执行的,而是单条执行。

  先上代码

@Test
  public void testAddUser() throws InterruptedException{
      for (int i = 0;i<20;i++) {
          User user = new User();
          user.setUserId(String.valueOf(i));
//          user.setUserName("xdp_gacl_白虎神皇");
          user.setUserBirthday(new Date().toString());
          user.setUserSalary((double) (10 + i));
          userService.addUser(user);
      }
      Thread.sleep(5000);
  }
@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addUser(User user) {
        
//        try {
//        ((UserServiceI) AopContext.currentProxy()).addUserScore();
//        }catch(Exception e) {
////            
//        }
        userMapper.insert(user);
//        int t = 1/0;
        
    }

  有趣的现象就是 我发现每次都会走到 ,注意是每次,说明每次执行  userService.addUser(user); 都会重新搞一次SqlSession,我猜测是userMapper的insert 都会重新获取SqlSession执行

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

   那么怎么才能使用batch方式呢,我这里只找到直接通过mybatis方式,没法通过spring的方式

@Test
  public void saveDeptBatchOne() throws Exception {
      SqlSessionFactory sessionFactory = SpringContextUtils.getBean("sqlSessionFactory", SqlSessionFactory.class);
      SqlSession session = sessionFactory.openSession();
      try {
          UserMapper deptMapper = (UserMapper) session.getMapper(UserMapper.class);
          long start =System.currentTimeMillis();
          for (int i = 0; i <100 ; i++) {
              User user = new User();
              user.setUserId(String.valueOf(i));
              user.setUserBirthday(new Date().toString());
              user.setUserSalary((double) (10 + i));
              deptMapper.insert(user);
          }
          long end =System.currentTimeMillis();
          System.out.println("BATCH耗时:"+(end-start));
          //非BATCH批量耗时 耗时:938
      } catch (Exception e) {
          e.printStackTrace();
      } finally {
          session.commit();
          session.close();
      }
  }

经过测试 通过batch方式的耗时是550ms-620ms

     而通过spring方式耗时 1200ms

  顺带提一下BATCH方式的原理和一个不大不小的问题

  原理

  都知道mybatis是对jdbc的封装,mybatis中的statement默认的类型是 PreparedStatement

      Class.forName("com.mysql.jdbc.Driver");
         Connection conn = DriverManager.getConnection(dbUrl, user, password);
         PreparedStatement pstmt = conn.prepareStatement("update content set introtext=? where id=?");

         for(int i=0; i<10000; i++){
             pstmt.setString(1, "abc"+i);
             pstmt.setInt(2, id);
             pstmt.addBatch();//添加到同一个批处理中
         }

         pstmt.executeBatch();//执行批处理

  上面就是jdbc使用batch的方式,再看看BatchExecutor是怎么做的

 @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection);
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // handler.parameterize(stmt);
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }
 @Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.addBatch();
  }

  batch什么时候执行呢?在SqlSession的commit里会调用Executor的 doFlushStatements

public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      List<BatchResult> results = new ArrayList<BatchResult>();
      if (isRollback) {
        return Collections.emptyList();
      }
      for (int i = 0, n = statementList.size(); i < n; i++) {//遍历每个Statement
        Statement stmt = statementList.get(i);
        BatchResult batchResult = batchResultList.get(i);
        try {
          batchResult.setUpdateCounts(stmt.executeBatch());

  如果要使用batch带来的好处,有一点必须注意,获取数据源jdbc连接必须带上 rewriteBatchedStatements=true

jdbc:mysql://10.3x.1x6.1x1:3306/rxxxx1?rewriteBatchedStatements=true

  batch方式有什么问题呢?有

  1 如果是自增主键,do类带不回来

  2 并不会自动提交,必须  session.commit();

下一篇就是分析源码了,为啥执行mapper的方法每次都创建sqlsession

  

posted on 2020-11-05 11:25  MaXianZhe  阅读(998)  评论(2编辑  收藏  举报

导航