在介绍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中的批处理是如何处理的。
主要的处理过程:
- 首先指定ExecutorType.BATCH打开sqlSession
- 多长重复执行单挑SQL
- 执行结束后调用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); } }
通过上文中的代码可以发现,在使用批处理的时候有几点需要注意:
- 在批处理过程中不要有查询SQL,如果有需要使用不同的executor
- 在批量处理过程中,相同的SQL要一起处理,不要有不同SQL间隔添加到批处理中
- 在大量的执行过程中可以部分执行flush,最后执行事务提交