在用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:
相关推荐
本文将详细介绍如何使用MyBatis拦截器来实现通用权限字段添加,达到灵活、可靠、可维护的数据库操作。 MyBatis拦截器是什么 MyBatis拦截器是一个接口,用于拦截MyBatis的Executor对象,以便在执行SQL语句之前或...
除了上述基本的实现方式,还可以考虑结合MyBatis的`ExecutorType.SIMPLE`或`ExecutorType.BATCH`执行器,以及`boundSql`对象来优化分页性能。例如,可以避免在`SIMPLE`模式下对每个分页请求都进行数据库连接的创建和...
在Mybatis框架中,拦截器(Interceptor)是一个强大的工具,它可以让我们在Mybatis执行SQL之前或之后做一些额外的操作,比如日志记录、权限检查、事务管理等。在本主题中,我们将深入探讨如何利用Mybatis的拦截器...
Mybatis拦截器(Interceptor)是一种插件机制,它允许我们在Mybatis执行SQL语句之前或之后进行自定义操作,比如统计SQL执行时间、添加日志等。拦截器基于Java的动态代理实现,可以拦截Mapper接口方法的调用。 接...
在本文中,我们将深入探讨如何在Spring Boot应用中整合MyBatis,实现MySQL数据库连接,以及如何利用Spring MVC和拦截器来实现国际化(i18n)功能。此外,我们还将提及IIS 12作为可能的Web服务器选项。 首先,Spring...
在 MyBatis 中,拦截器需要实现 `org.apache.ibatis.plugin.Interceptor` 接口,并重写 `intercept` 方法。这个方法会在目标方法执行前后被调用,参数 `Invocation` 包含了目标方法的信息,包括方法对象、方法参数等...
1. 创建一个自定义的MyBatis插件,实现`Interceptor`接口,并重写`intercept`方法。在这个方法中,根据注解判断是否需要对参数或结果集进行处理。 2. 在`Configuration`类中注册这个插件,确保在MyBatis执行SQL时能...
1. 创建自定义拦截器类,实现`HandlerInterceptor`接口,重写`preHandle`、`postHandle`和`afterCompletion`方法。 2. 在`WebMvcConfigurer`的实现类中,通过`addInterceptors`方法添加拦截器,指定拦截的URL和排除...
在自动封装版的MyBatis分页拦截器中,开发者通常会创建一个拦截器类,该类会拦截到执行SQL的时机,然后在SQL语句中动态添加分页相关的条件,如LIMIT和OFFSET子句,以实现数据的分页展示。 分页拦截器的核心思想是...
Mybatis作为一款强大的Java持久层框架,其灵活性使得扩展分库分表功能变得可能。本文将深入探讨Mybatis分库分表扩展插件的原理与实践。 一、Mybatis简介与分库分表需求 Mybatis是一款优秀的持久层框架,它支持定制...
定义好的拦截器会按照顺序被Mybatis执行。 由于Mybatis的架构设计,拦截器的实现和使用相对灵活。可以通过实现Interceptor接口并重写其方法来自定义拦截逻辑,还可以通过@Intercepts和@Signature注解来指定拦截器...
通过实现Interceptor接口并重写intercept方法,开发者可以实现自定义的拦截逻辑。 4. **Free MyBatis Plugins的功能**:这款免费插件可能包含以下功能: - SQL智能提示:在编写Mapper XML文件或者Mapper接口时,...
Spring Boot以其快速启动、自动化配置和开箱即用的特点,大大简化了Java应用程序的开发过程,而MyBatis作为轻量级的持久层框架,提供了灵活的SQL映射机制,使得数据库操作更为便捷。在这个教程中,我们将不仅了解这...
在 JavaFX 应用中整合 SpringBoot 和 MyBatis 可以让开发者利用 Spring 的自动化配置和依赖注入,以及 MyBatis 的灵活数据库操作,构建出高效且易于维护的桌面应用。 整合 JavaFX、SpringBoot 和 MyBatis 的主要...
mybatis-pagination是一款针对MyBatis的分页插件,它通过拦截器的方式,实现了对原生MyBatis的动态SQL进行拦截,自动添加分页相关的SQL语句,从而实现数据库的分页查询。此插件支持多种主流数据库,如MySQL、Oracle...
分页拦截器需要实现MyBatis提供的Interceptor接口,并重写intercept方法,以便在SQL执行前进行拦截。在intercept方法中,需要根据Page对象计算出的参数(dbIndex和dbNumber)来修改原始的SQL语句,添加分页逻辑。...
总的来说,理解 MyBatis 插件原理和自定义插件的编写,可以帮助我们更好地扩展 MyBatis 的功能,实现更灵活的数据库操作。同时,掌握 Spring 集成 MyBatis 的方法,可以确保事务管理和资源管理的正确性,提升系统的...
以前曾经用过ibatis,这是mybatis的前身,当时在做项目时,感觉很不错,比hibernate灵活。性能也比hibernate好。而且也比较轻量级,因为当时在项目中,没来的及做很很多笔记。后来项目结束了,我也没写总结文档。已经...
这些拦截器可以注册到MyBatis的配置文件中,当MyBatis执行SQL时,会按照配置的顺序逐个调用拦截器的intercept()方法。 在下载并解压“mybatis-plugins”压缩包后,你会得到一个名为“mybatis-plugins”的文件,这个...
通过深入研究源码、理解代码生成的流程,我们可以灵活地定制代码生成器,实现前端页面、VO对象、DTO对象等自定义代码的生成,从而提高开发效率,降低维护成本。在实践中,不断尝试和调试,结合官方文档和社区资源,...