扩展mybatis重写mybatis执行器实现灵活批处理
在用mybatis作为持久层框架时,有时候会有需要进行批量增删改的操作。
百度了一下,大致有两种方法,一种是拼接SQL的方式。类似这样:
<insert id="insertList" parameterType="java.util.List" > insert into t_project ( projectid,productid ) values <foreach collection="list" item="item" index="index" separator="," > ( #{item.projectid,jdbcType=CHAR},#{item.productid,jdbcType=CHAR} ) </foreach> </insert>
这种方法局限性太多,sql长度有限制,clob类型不能这样使用,貌似只能用于insert语句。所以完全不考虑。
第二种是用mybatis提供的BatchExecutor进行批处理操作。类似这样:
SqlSession session =sessionFactory.openSession(ExecutorType.BATCH,false); for(int i=0;i<10;i++){ session.update("com.com.hcd.mybatis.interfaces.CommonMapper.update",getParam()); } session.flushStatements(); session.commit();
这里的"com.com.hcd.mybatis.interfaces.CommonMapper.update"就是xml配置里namespace加<update>标签的id或mapper接口的class名+方法名。这种方式新开启了一个新事务,在mybatis和spring结合的情况下,这个事务没有被spring控制到。只能手动进行提交。
如果要让spring控制批处理事务,那只能所有的事务都使用批处理模式,即SqlSessionTemplate设置executorType为batch,但是这样会有问题,如:使用数据库自增主键则无法获得返回的主键,我这边的系统ID都是在java内存生成uuid后再插入到数据库的,所以这个没有影响;增删改操作无法返回影响行数,因为batchExecutor中update方法默认放回的是一个int常量。(其原因都是因为此时sql尚未在数据库执行,所以无法获得这些信息)。
所以,这个方法也不好。根本原因是mybatis在创建SqlSession的时候,去实例化executor时,就决定了执行sql的方式是不是batch方式。在实例化SImpleExector的时候,则表明该事务的所有增删改方法会立刻在数据库执行该语句,实例化BatchExector的时候,则表明该事务的所有增删改方法都是 以batch方式,不会立刻在数据库执行。即mybatis框架本身限制了你不能灵活的在一次事务中选择是否要以批处理的方式执行。
所以,我们就会想着去拓展mybatis框架,我这边mybatis版本是3.2.2。如果BatchExector在执行update方法后立刻执行doFlushStatements,则相当于SImpleExector下执行update方法。即batchExector工作机制已经满足我们的需求,想法是扩展新的执行器继承BatchExector,重写doUpdate方法,判断是否要以批处理方式执行,对应是否调用doFlushStatements。
下面是做了一个简单的测试,测试要点是想看用批处理做频繁的更新操作和非批处理做频繁的更新操作,效率差多少,BatchExector执行addBatch后立刻doFlushStatements,和SImpleExector执行update方法是否存在性能的差距。测试用例如下:
package com.hcd.mybatis.demo; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * Created by cd_huang on 2017/8/21. */ @Component public class BatchMybatisTest { @Autowired private SqlSessionFactory sessionFactory; public void test(){ int group =26; long times[][] =new long[group][3]; for(int i=0;i<group;i++){ times[i][0] = batchExecutorBatchExecute(10+i*50); times[i][1] =batchExecutorSimpleExecute(10+i*50); times[i][2] =simpleExecutorSimpleExecute(10+i*50); } for(int i=1;i<group;i++){ System.out.println("Batch - Executor - Batch - Execute: " + times[i][0] + " ms"); System.out.println("Batch - Executor - Simple - Execute: " + times[i][1] + " ms"); System.out.println("Simple - Executor - Simple - Execute: " + times[i][2] + " ms"); } } public long batchExecutorBatchExecute(int group){ SqlSession session =sessionFactory.openSession(ExecutorType.BATCH,false); long time = System.currentTimeMillis(); for(int i=0;i<group;i++){ session.update("com.com.hcd.mybatis.interfaces.CommonMapper.update",getParam()); } session.flushStatements(); time = System.currentTimeMillis() - time; session.commit(); return time; } public long batchExecutorSimpleExecute(int group){ SqlSession session =sessionFactory.openSession(ExecutorType.BATCH,false); long time = System.currentTimeMillis(); for(int i=0;i<group;i++){ session.update("com.com.hcd.mybatis.interfaces.CommonMapper.update",getParam()); session.flushStatements(); } time = System.currentTimeMillis() - time; session.commit(); return time; } public long simpleExecutorSimpleExecute(int group){ SqlSession session =sessionFactory.openSession(); long time = System.currentTimeMillis(); for(int i=0;i<group;i++){ session.update("com.com.hcd.mybatis.interfaces.CommonMapper.update",getParam()); } time = System.currentTimeMillis() - time; session.commit(); return time; } private static Map<String,Object> getParam(){ Map param=new HashMap(); return param; } }执行结果如下:
Batch - Executor - Batch - Execute: 123 ms Batch - Executor - Simple - Execute: 271 ms Simple - Executor - Simple - Execute: 197 ms Batch - Executor - Batch - Execute: 181 ms Batch - Executor - Simple - Execute: 524 ms Simple - Executor - Simple - Execute: 596 ms Batch - Executor - Batch - Execute: 238 ms Batch - Executor - Simple - Execute: 664 ms Simple - Executor - Simple - Execute: 523 ms Batch - Executor - Batch - Execute: 370 ms Batch - Executor - Simple - Execute: 747 ms Simple - Executor - Simple - Execute: 814 ms Batch - Executor - Batch - Execute: 311 ms Batch - Executor - Simple - Execute: 688 ms Simple - Executor - Simple - Execute: 661 ms Batch - Executor - Batch - Execute: 357 ms Batch - Executor - Simple - Execute: 799 ms Simple - Executor - Simple - Execute: 765 ms Batch - Executor - Batch - Execute: 431 ms Batch - Executor - Simple - Execute: 917 ms Simple - Executor - Simple - Execute: 878 ms Batch - Executor - Batch - Execute: 471 ms Batch - Executor - Simple - Execute: 991 ms Simple - Executor - Simple - Execute: 848 ms Batch - Executor - Batch - Execute: 339 ms Batch - Executor - Simple - Execute: 1004 ms Simple - Executor - Simple - Execute: 1087 ms Batch - Executor - Batch - Execute: 820 ms Batch - Executor - Simple - Execute: 1082 ms Simple - Executor - Simple - Execute: 1443 ms Batch - Executor - Batch - Execute: 746 ms Batch - Executor - Simple - Execute: 1535 ms Simple - Executor - Simple - Execute: 1514 ms Batch - Executor - Batch - Execute: 875 ms Batch - Executor - Simple - Execute: 1696 ms Simple - Executor - Simple - Execute: 1677 ms Batch - Executor - Batch - Execute: 810 ms Batch - Executor - Simple - Execute: 1626 ms Simple - Executor - Simple - Execute: 1576 ms Batch - Executor - Batch - Execute: 748 ms Batch - Executor - Simple - Execute: 1506 ms Simple - Executor - Simple - Execute: 1375 ms Batch - Executor - Batch - Execute: 751 ms Batch - Executor - Simple - Execute: 1544 ms Simple - Executor - Simple - Execute: 1387 ms Batch - Executor - Batch - Execute: 689 ms Batch - Executor - Simple - Execute: 1536 ms Simple - Executor - Simple - Execute: 1490 ms Batch - Executor - Batch - Execute: 772 ms Batch - Executor - Simple - Execute: 1995 ms Simple - Executor - Simple - Execute: 2242 ms Batch - Executor - Batch - Execute: 1143 ms Batch - Executor - Simple - Execute: 2530 ms Simple - Executor - Simple - Execute: 2708 ms Batch - Executor - Batch - Execute: 1705 ms Batch - Executor - Simple - Execute: 2600 ms Simple - Executor - Simple - Execute: 2745 ms Batch - Executor - Batch - Execute: 1427 ms Batch - Executor - Simple - Execute: 2516 ms Simple - Executor - Simple - Execute: 2574 ms Batch - Executor - Batch - Execute: 1191 ms Batch - Executor - Simple - Execute: 2157 ms Simple - Executor - Simple - Execute: 2142 ms Batch - Executor - Batch - Execute: 708 ms Batch - Executor - Simple - Execute: 2170 ms Simple - Executor - Simple - Execute: 2173 ms Batch - Executor - Batch - Execute: 818 ms Batch - Executor - Simple - Execute: 2415 ms Simple - Executor - Simple - Execute: 2419 ms Batch - Executor - Batch - Execute: 1140 ms Batch - Executor - Simple - Execute: 2570 ms Simple - Executor - Simple - Execute: 2244 ms Batch - Executor - Batch - Execute: 785 ms Batch - Executor - Simple - Execute: 2205 ms Simple - Executor - Simple - Execute: 2181 ms可以看出来,批处理模式下,进行大量update操作时,性能差不多可以提升60-70%。且BatchExector执行addBatch后立刻doFlushStatements,和SImpleExector执行update方法消耗的时候非常接近。
测试表明了我们拓展mybatis的框架思路是有可行性的。下面我直接贴代码介绍下我如何拓展mybatis框架实现灵活批处理。
扩展入口是利用mybatis提供的拦截器机制,实现自定义的代理执行器对象。需要在mybatis-config.xml里配置
<plugin interceptor="com.com.hcd.mybatis.exector.ProxyExecutorInterceptor" />
ProxyExecutorInterceptor:
package com.hcd.mybatis.exector; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Invocation; import java.lang.reflect.Proxy; import java.util.Properties; /** * 使用插件生成代理执行器 * Created by cd_huang on 2017/8/22. */ public class ProxyExecutorInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { return null; } @Override public Object plugin(Object target) { if (target instanceof Executor == false) { return target; } return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new ProxyExecutorHandler((Executor)target)); } @Override public void setProperties(Properties properties) { } }这里没用默认的Plugin.wrap()来代理对象,而是用我们自定义的ProxyExecutorHandler来进行代理控制。
ProxyExecutorHandler:
package com.hcd.mybatis.exector; import com.hcd.common.SpringContextUtil; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.ibatis.executor.*; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.reflection.ReflectionException; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSessionFactory; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 代理拦截器,控制ComplexExecutor的初始化时机 * Created by cd_huang on 2017/8/23. */ public class ProxyExecutorHandler implements InvocationHandler { private Executor target; private Executor realTarget; private boolean isInit = false; public ProxyExecutorHandler(Executor target) { this.target = target; MybatisExecutorContext.bindExecutor(this); if (MybatisExecutorContext.initComplexExecutorImmediately) { init(); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (!isInit && MybatisExecutorContext.isOpenExecutorMode()) { init(); } try { return method.invoke(this.target, args); } finally { String methodName = method.getName(); if ("commit".equals(methodName) || "rollback".equals(methodName) || "close".equals(methodName)) { MybatisExecutorContext.clean(); } } } public void init() { this.target = initComplexExecutor(this.target); isInit = true; } /** * 初始化ComplexExecutor * * @param target * @return */ public Executor initComplexExecutor(Executor target) { Executor initialTarget = target; Object[] result = ProxyExecutorHandler.getLastPluginFromJdkProxy(target); Object parentPlugin = result[0]; target = (Executor) result[1]; if (target instanceof ComplexExecutor) { realTarget =target; return initialTarget; } if (target instanceof BaseExecutor) { ComplexExecutor newTarget = new ComplexExecutor(ProxyExecutorHandler.getConfiguration(), target.getTransaction(), getOriginalExecutorType((Executor)target)); realTarget =newTarget; if (parentPlugin == null) { return newTarget; } else { //替换掉Plugin的target属性 try { Field targetField = FieldUtils.getField(Plugin.class, "target", true); FieldUtils.writeField(targetField, parentPlugin, newTarget, true); return initialTarget; } catch (Throwable e) { throw new ReflectionException("replace property 'target' of Plugin error !", e); } } } //替换掉CachingExecutor的delegate属性 if (target instanceof CachingExecutor) { try { Field delegateField = FieldUtils.getField(CachingExecutor.class, "delegate", true); Object delegate = FieldUtils.readField(delegateField, target, true); if (delegate instanceof BaseExecutor) { Executor newDelegate = new ComplexExecutor(ProxyExecutorHandler.getConfiguration(), ((BaseExecutor) delegate).getTransaction(), getOriginalExecutorType((Executor)delegate)); realTarget =newDelegate; FieldUtils.writeField(delegateField, target, newDelegate, true); return initialTarget; } } catch (Throwable e) { throw new ReflectionException("replace property 'delegate' of CachingExecutor error !", e); } } throw new ExecutorException("init ComplexExecutor error !"); } /** * 获得jdk代理里最里层的一个Plugin对象 * * @param proxy * @return 返回 最里层的一个Plugin对象 和 Plugin的target属性 */ public static Object[] getLastPluginFromJdkProxy(Object proxy) { if (proxy instanceof Proxy) { InvocationHandler h = Proxy.getInvocationHandler(proxy); Object target; try { Field targetField = FieldUtils.getField(Plugin.class, "target", true); target = FieldUtils.readField(targetField, h, true); } catch (Throwable e) { throw new ReflectionException("get property 'target' of Plugin error !", e); } if (target instanceof Proxy) { return ProxyExecutorHandler.getLastPluginFromJdkProxy(target); } else { return new Object[]{h, target}; } } else { return new Object[]{null, proxy}; } } private static Configuration configuration; public static Configuration getConfiguration() { if (configuration == null) { //从spring上下文中拿到SqlSessionFactory,从而拿到configuration configuration = SpringContextUtil.getBean(SqlSessionFactory.class).getConfiguration(); } return configuration; } public ExecutorType getOriginalExecutorType() { if(realTarget!=null){ return getOriginalExecutorType(realTarget); } throw new ExecutorException("ComplexExecutor not init !"); } public static ExecutorType getOriginalExecutorType(Executor executor) { if(executor instanceof ComplexExecutor){ return ((ComplexExecutor)executor).getOriginalExecutorType(); } if (BatchExecutor.class.getName().equals(executor.getClass().getName())) { return ExecutorType.BATCH; } else { return ExecutorType.SIMPLE; } } public Executor getRealTarget(){ return realTarget; } public Executor getTarget() { return target; } public void setTarget(Executor target) { this.target = target; } }
主要是控制把SimpleExector或BatchExector替换成我们自定义的执行器对象ComplexExecutor。
ComplexExecutor:
package com.hcd.mybatis.exector; import org.apache.ibatis.executor.BatchExecutor; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.transaction.Transaction; import java.sql.SQLException; import java.util.List; /** * 扩展mybatis支持批处理和非批处理两种模式的复合执行器 * Created by cd_huang on 2017/8/22. */ public class ComplexExecutor extends BatchExecutor{ private ExecutorType originalExecutorType; public ComplexExecutor(Configuration configuration, Transaction transaction,ExecutorType originalExecutorType) { super(configuration, transaction); this.originalExecutorType =originalExecutorType; } public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException { int result =super.doUpdate(ms,parameterObject); if(!MybatisExecutorContext.isBatchExecutorMode()){ List<BatchResult> results=this.doFlushStatements(false); return results.get(0).getUpdateCounts()[0]; } return result; } public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { List<BatchResult> results =super.doFlushStatements(isRollback); MybatisExecutorContext.getCheckBatchResultHook().checkBatchResult(results); return results; } public ExecutorType getOriginalExecutorType() { return originalExecutorType; } }该执行器重写了doUpdate()方法,判断是否要使用批处理模式,从而判断是否执行doFlushStatements。 判断是否要执行批处理模式,是在MybatisExecutorContext类中,利用线程变量,来主动设置的。MybatisExecutorContext:
package com.hcd.mybatis.exector; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.session.ExecutorType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.SQLException; import java.util.Collections; import java.util.List; /** * mybatis执行器上下文 * Created by cd_huang on 2017/8/22. */ public class MybatisExecutorContext { private static Logger logger = LoggerFactory.getLogger(MybatisExecutorContext.class); private static final ThreadLocal<ExecutorType> currentExecutorType = new ThreadLocal<>(); private static final ThreadLocal<ProxyExecutorHandler> executorResource = new ThreadLocal<>(); private static final ThreadLocal<CheckBatchResultHook> checkBatchResultHook = new ThreadLocal<>(); /** * 是否立刻初始化ComplexExecutor */ public static boolean initComplexExecutorImmediately =true; public static void openSimpleExecutorMode(){ currentExecutorType.set(ExecutorType.SIMPLE); } public static void openBatchExecutorMode(){ currentExecutorType.set(ExecutorType.BATCH); } public static void closeExecutorMode(){ currentExecutorType.remove(); } public static List<BatchResult> doFlushStatements(){ ProxyExecutorHandler interceptor =executorResource.get(); if(interceptor==null){ return Collections.emptyList(); } Executor executor =interceptor.getRealTarget(); if(executor instanceof ComplexExecutor){ try { return ((ComplexExecutor)executor).doFlushStatements(false); } catch (SQLException e) { throw new RuntimeException("doFlushStatements error!",e); } } throw new ExecutorException("doFlushStatements must invoke on ExecutorType.BATCH!"); } public static void bindExecutor(ProxyExecutorHandler interceptor){ executorResource.set(interceptor); } public static boolean isOpenExecutorMode(){ return currentExecutorType.get()!=null; } public static Boolean isBatchExecutorMode() { ExecutorType executorType =currentExecutorType.get(); if(executorType==null){ ProxyExecutorHandler interceptor =executorResource.get(); executorType =interceptor==null?null:interceptor.getOriginalExecutorType(); } return ExecutorType.BATCH.equals(executorType); } public static void clean(){ currentExecutorType.remove(); executorResource.remove(); } private static CheckBatchResultHook defaultCheckBatchResultHook =new DefaultCheckBatchResultHook(); public static CheckBatchResultHook getCheckBatchResultHook(){ CheckBatchResultHook hook =checkBatchResultHook.get(); return hook==null?defaultCheckBatchResultHook:hook; } public static void setCheckBatchResultHook(CheckBatchResultHook hook){ checkBatchResultHook.set(hook); } /** * 校验批处理结果的默认机制(默认更新影响记录数为0的时候logger打印警告信息) */ private static class DefaultCheckBatchResultHook implements CheckBatchResultHook{ @Override public boolean checkBatchResult(List<BatchResult> results) { for(BatchResult result:results){ for(int i=0;i<result.getUpdateCounts().length;i++){ if(result.getUpdateCounts()[i]<=0){ logger.warn(" sql statementId "+result.getMappedStatement().getId()+","+result.getMappedStatement().getSqlCommandType().name()+" effect 0 rows ! "); } } } return true; } } }该执行器还重写了doFlushStatements方法,调用了MybatisExecutorContext.getCheckBatchResultHook().checkBatchResult(results);
主要是用于可以自定义校验方法去校验批处理模式下的影响行数是否符合预计。
CheckBatchResultHook:
package com.hcd.mybatis.exector; import org.apache.ibatis.executor.BatchResult; import java.util.List; /** * 校验批处理结果 * Created by cd_huang on 2017/8/24. */ public interface CheckBatchResultHook { boolean checkBatchResult(List<BatchResult> results); }
默认的校验实现在MybatisExecutorContext的内部类DefaultCheckBatchResultHook中,默认增删改的影响行数小于等于0时,打印警告日志。这里可以看是否需要改成直接抛异常。
然后用新的执行器在complexExecutor,spring事务里执行,测试用例BatchMybatisTest2:
package com.hcd.mybatis.demo; import com.hcd.common.SpringContextUtil; import com.hcd.mybatis.exector.MybatisExecutorContext; import com.hcd.mybatis.interfaces.CommonMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import java.util.HashMap; import java.util.Map; /** * Created by cd_huang on 2017/8/28. */ @Component public class BatchMybatisTest2 { @Autowired private CommonMapper commonMapper; private TransactionTemplate transactionTemplate; private TransactionTemplate getTransactionTemplate(){ if(transactionTemplate==null){ transactionTemplate =new TransactionTemplate(); transactionTemplate.setTransactionManager(SpringContextUtil.getBean(PlatformTransactionManager.class)); transactionTemplate.setPropagationBehavior(3); } return transactionTemplate; } public void test(){ int group =26; long times[][] =new long[group][3]; for(int i=0;i<group;i++){ times[i][0] = batchExecutorBatchExecute(10+i*50); times[i][1] =batchExecutorSimpleExecute(10+i*50); times[i][2] =simpleExecutorSimpleExecute(10+i*50); } for(int i=1;i<group;i++){ System.out.println("Batch - Executor - Batch - Execute: " + times[i][0] + " ms"); System.out.println("Batch - Executor - Simple - Execute: " + times[i][1] + " ms"); System.out.println("Simple - Executor - Simple - Execute: " + times[i][2] + " ms"); } } public long batchExecutorBatchExecute(final int group){ return getTransactionTemplate().execute( new TransactionCallback<Long>(){ @Override public Long doInTransaction(TransactionStatus status) { MybatisExecutorContext.openBatchExecutorMode(); long time = System.currentTimeMillis(); for(int i=0;i<group;i++){ commonMapper.update(); } MybatisExecutorContext.doFlushStatements(); time = System.currentTimeMillis() - time; return time; } }); } public long batchExecutorSimpleExecute(final int group){ return getTransactionTemplate().execute( new TransactionCallback<Long>(){ @Override public Long doInTransaction(TransactionStatus status) { long time = System.currentTimeMillis(); for(int i=0;i<group;i++){ commonMapper.update(); } time = System.currentTimeMillis() - time; return time; } }); } public long simpleExecutorSimpleExecute(final int group){ MybatisExecutorContext.initComplexExecutorImmediately =false; return getTransactionTemplate().execute( new TransactionCallback<Long>(){ @Override public Long doInTransaction(TransactionStatus status) { long time = System.currentTimeMillis(); for(int i=0;i<group;i++){ commonMapper.update(); } time = System.currentTimeMillis() - time; return time; } }); } }
测试结果如下:
Batch - Executor - Batch - Execute: 62 ms Batch - Executor - Simple - Execute: 121 ms Simple - Executor - Simple - Execute: 148 ms Batch - Executor - Batch - Execute: 79 ms Batch - Executor - Simple - Execute: 254 ms Simple - Executor - Simple - Execute: 219 ms Batch - Executor - Batch - Execute: 78 ms Batch - Executor - Simple - Execute: 327 ms Simple - Executor - Simple - Execute: 281 ms Batch - Executor - Batch - Execute: 113 ms Batch - Executor - Simple - Execute: 387 ms Simple - Executor - Simple - Execute: 388 ms Batch - Executor - Batch - Execute: 101 ms Batch - Executor - Simple - Execute: 504 ms Simple - Executor - Simple - Execute: 495 ms Batch - Executor - Batch - Execute: 137 ms Batch - Executor - Simple - Execute: 526 ms Simple - Executor - Simple - Execute: 559 ms Batch - Executor - Batch - Execute: 154 ms Batch - Executor - Simple - Execute: 606 ms Simple - Executor - Simple - Execute: 631 ms Batch - Executor - Batch - Execute: 198 ms Batch - Executor - Simple - Execute: 699 ms Simple - Executor - Simple - Execute: 793 ms Batch - Executor - Batch - Execute: 227 ms Batch - Executor - Simple - Execute: 858 ms Simple - Executor - Simple - Execute: 851 ms Batch - Executor - Batch - Execute: 185 ms Batch - Executor - Simple - Execute: 812 ms Simple - Executor - Simple - Execute: 942 ms Batch - Executor - Batch - Execute: 227 ms Batch - Executor - Simple - Execute: 1057 ms Simple - Executor - Simple - Execute: 1528 ms Batch - Executor - Batch - Execute: 243 ms Batch - Executor - Simple - Execute: 1037 ms Simple - Executor - Simple - Execute: 993 ms Batch - Executor - Batch - Execute: 158 ms Batch - Executor - Simple - Execute: 1091 ms Simple - Executor - Simple - Execute: 1083 ms Batch - Executor - Batch - Execute: 264 ms Batch - Executor - Simple - Execute: 1391 ms Simple - Executor - Simple - Execute: 1133 ms Batch - Executor - Batch - Execute: 160 ms Batch - Executor - Simple - Execute: 1232 ms Simple - Executor - Simple - Execute: 1247 ms Batch - Executor - Batch - Execute: 224 ms Batch - Executor - Simple - Execute: 1287 ms Simple - Executor - Simple - Execute: 1344 ms Batch - Executor - Batch - Execute: 207 ms Batch - Executor - Simple - Execute: 1379 ms Simple - Executor - Simple - Execute: 1367 ms Batch - Executor - Batch - Execute: 280 ms Batch - Executor - Simple - Execute: 1473 ms Simple - Executor - Simple - Execute: 1469 ms Batch - Executor - Batch - Execute: 344 ms Batch - Executor - Simple - Execute: 1544 ms Simple - Executor - Simple - Execute: 1582 ms Batch - Executor - Batch - Execute: 511 ms Batch - Executor - Simple - Execute: 1747 ms Simple - Executor - Simple - Execute: 1612 ms Batch - Executor - Batch - Execute: 263 ms Batch - Executor - Simple - Execute: 1772 ms Simple - Executor - Simple - Execute: 1812 ms Batch - Executor - Batch - Execute: 343 ms Batch - Executor - Simple - Execute: 1799 ms Simple - Executor - Simple - Execute: 1906 ms Batch - Executor - Batch - Execute: 340 ms Batch - Executor - Simple - Execute: 1855 ms Simple - Executor - Simple - Execute: 1833 ms Batch - Executor - Batch - Execute: 354 ms Batch - Executor - Simple - Execute: 1925 ms Simple - Executor - Simple - Execute: 1932 ms Batch - Executor - Batch - Execute: 381 ms Batch - Executor - Simple - Execute: 2014 ms Simple - Executor - Simple - Execute: 2184 ms
如预期所料,实现了灵活的批处理效果。