插件编写(plugin)
步骤:
1. 编写Interceptor的实现类:
MyFirstPlug
2. 使用@Intercepts注解完成插件签名
3. 将写好的插件注册到全局配置文件中
package com.feng.config; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.util.Properties; /** * @Desc:完成插件签名: * 告诉mybatis当前插件用来拦截哪个对象的哪个方法 * @Param: Sinature type:拦截哪个对象(四大对象) * method:对象的哪个方法 * args: 当前方法的参数列表 */ @Intercepts({ @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class) }) public class MyFirstPlug implements Interceptor { /** * 拦截目标对象的目标方法的执行 * @param invocation * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable {
//TODO ... 动态修改mybatis运行流程;比如sql分页、修改sql使用的参数。。。 //执行目标方法: parameterrize Object proceed = invocation.proceed(); //返回执行后的返回值
//TODO ... return proceed; }
/** * 包装目标对象,为目标对象创建一个代理对象 * @param target * @return */ @Override public Object plugin(Object target) { //借助Plugin的wrap方法来使用当前Interceptor包装目标对象 Object wrap = Plugin.wrap(target, this); //返回为当前target创建的动态代理对象 return wrap; } /** * 将插件注册时的property属性设置进来 * @param properties */ @Override public void setProperties(Properties properties) { System.out.println("插件配置信息:"+properties); String username = (String) properties.get("username"); String password = (String) properties.get("password"); } }
mybatis-config.xml
<configuration>
<!-- 配置全局插件 -->
<plugins>
<plugin interceptor="com.feng.config.MyFirstPlug">
<property name="username" value="zhangsan"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
</configuration>
插件扩展:
插件拦截的场景:
场景一:修改sql使用的参数
package com.feng.config; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.util.Properties; /** * @Desc:完成插件签名: * 告诉mybatis当前插件用来拦截哪个对象的哪个方法 * @Param: Sinature type:拦截哪个对象(四大对象) * method:对象的哪个方法 * args: 当前方法的参数列表 */ @Intercepts({ @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class) }) public class MyFirstPlug implements Interceptor { /** * 拦截目标对象的目标方法的执行 * @param invocation * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("intercept:"+invocation.getTarget()); //动态的改变一下sql的参数: Object target = invocation.getTarget(); System.out.println("当前拦截的对象:"+target); //拿到:StatementHandler ==>ParameterHandler ==>parameterObject //拿到target的元数据 MetaObject metaObject = SystemMetaObject.forObject(target); Object value = metaObject.getValue("parameterHandler.parameterObject"); System.out.println("sql语句的参数:"+value); //修改sql语句要用的参数 metaObject.setValue("parameterHandler.parameterObject", 12); //返回执行后的返回值 Object proceed = invocation.proceed(); return proceed; } /** * 包装目标对象,为目标对象创建一个代理对象 * @param target * @return */ @Override public Object plugin(Object target) { //借助Plugin的wrap方法来使用当前Interceptor包装目标对象 Object wrap = Plugin.wrap(target, this); //返回为当前target创建的动态代理对象 return wrap; } /** * 将插件注册时的property属性设置进来 * @param properties */ @Override public void setProperties(Properties properties) { System.out.println("插件配置信息:"+properties); String username = (String) properties.get("username"); String password = (String) properties.get("password"); } }
场景二:PageHelper插件进行分页
a . 引入依赖
在pom.xml文件添加如下依赖:
<!-- mybatis分页插件依赖 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.2.0</version> </dependency>
b. 在Mybatis配置xml中配置拦截器插件
在mybatis-config.xml文件中添加插件后的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> <!-- 该参数默认为false --> <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 --> <!-- 和startPage中的pageNum效果一样--> <property name="offsetAsPageNum" value="true"/> <!-- 该参数默认为false --> <!-- 设置为true时,使用RowBounds分页会进行count查询 --> <property name="rowBoundsWithCount" value="true"/> <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 --> <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)--> <property name="pageSizeZero" value="true"/> <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 --> <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 --> <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 --> <property name="reasonable" value="false"/> <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 --> <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 --> <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 --> <!-- 不理解该含义的前提下,不要随便复制该配置 --> <property name="params" value="pageNum=start;pageSize=limit;"/> <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page --> <property name="returnPageInfo" value="check"/> </plugin> </plugins> </configuration>
c. 默认jar包中的实现类
package com.github.pagehelper; import com.github.pagehelper.util.MSUtils; import com.github.pagehelper.util.StringUtil; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; /** * 最简单的分页插件拦截器 */ @SuppressWarnings("rawtypes") @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})) public class PaginationInterceptor implements Interceptor { protected Dialect dialect; protected Field additionalParametersField; @Override public Object intercept(Invocation invocation) throws Throwable { //获取拦截方法的参数 Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameterObject = args[1]; RowBounds rowBounds = (RowBounds) args[2]; List resultList; //调用方法判断是否需要进行分页,如果不需要,直接返回结果 if (!dialect.skip(ms, parameterObject, rowBounds)) { ResultHandler resultHandler = (ResultHandler) args[3]; //当前的目标对象 Executor executor = (Executor) invocation.getTarget(); BoundSql boundSql = ms.getBoundSql(parameterObject); //反射获取动态参数 Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql); //判断是否需要进行 count 查询 if (dialect.beforeCount(ms, parameterObject, rowBounds)) { //创建 count 查询的缓存 key CacheKey countKey = executor.createCacheKey(ms, parameterObject, RowBounds.DEFAULT, boundSql); countKey.update("_Count"); //根据当前的 ms 创建一个返回值为 Long 类型的 ms MappedStatement countMs = MSUtils.newCountMappedStatement(ms); //调用方言获取 count sql String countSql = dialect.getCountSql(ms, boundSql, parameterObject, rowBounds, countKey); BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject); //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中 for (String key : additionalParameters.keySet()) { countBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } //执行 count 查询 Object countResultList = executor.query(countMs, parameterObject, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql); Long count = (Long) ((List) countResultList).get(0); //处理查询总数 dialect.afterCount(count, parameterObject, rowBounds); if (count == 0L) { //当查询总数为 0 时,直接返回空的结果 return dialect.afterPage(new ArrayList(), parameterObject, rowBounds); } } //判断是否需要进行分页查询 if (dialect.beforePage(ms, parameterObject, rowBounds)) { //生成分页的缓存 key CacheKey pageKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql); //处理参数对象 parameterObject = dialect.processParameterObject(ms, parameterObject, boundSql, pageKey); //调用方言获取分页 sql String pageSql = dialect.getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey); BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject); //设置动态参数 for (String key : additionalParameters.keySet()) { pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } //执行分页查询 resultList = executor.query(ms, parameterObject, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql); } else { resultList = new ArrayList(); } } else { args[2] = RowBounds.DEFAULT; resultList = (List) invocation.proceed(); } //返回默认查询 return dialect.afterPage(resultList, parameterObject, rowBounds); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialectClassStr = properties.getProperty("dialect"); if (StringUtil.isEmpty(dialectClassStr)) { throw new RuntimeException("使用 PaginationInterceptor 分页插件时,必须设置 dialect 属性"); } try { Class dialectClass = Class.forName(dialectClassStr); dialect = (Dialect) dialectClass.newInstance(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("初始化 dialect [" + dialectClassStr + "]时出错:" + e.getMessage()); } dialect.setProperties(properties); try { //反射获取 BoundSql 中的 additionalParameters 属性 additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters"); additionalParametersField.setAccessible(true); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } }
d.使用page
或
场景三:批量操作
与Spring整合进行批量操作
<!--整合mybatis 目的:1、Spring管理所有组件, 2、Spring用来管理事务 --> <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- configLocation 指定全局配置文件的位置--> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!--mapperLocations:指定mapper文件的位置--> <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property> </bean> <!-- 配置一个可以进行批量执行的sqlSession --> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg> <constructor-arg name="executorType" ref="BATCH"></constructor-arg> </bean>
java中使用:
@Autowired
private SqlSession sqlSession;
场景四:存储过程
场景五:typeHandler处理枚举
可以通过自定义TypeHandler的形式来在设置参数或取出结果集的时候自定义参数封装策略
步骤:
1. 实现TypeHandler接口或继承BaseTypeHandler
2. 使用@MappedTypes定义处理的java类型
3. 在自定义结果集标签或参数处理的时候声明使用自定义TypeHandler进行处理
或在全局配置TypeHandler要处理的javaType
多插件编写:
<configuration> <!-- 配置全局插件 --> <plugins> <plugin interceptor="com.feng.config.MyFirstPlug"> <property name="username" value="zhangsan"/> <property name="password" value="123456"/> </plugin> <plugin interceptor="com.feng.config.MySecondPlug"></plugin> </plugins> </configuration>