与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