Mybatis 之 插件
插件编写(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
username" value="zhangsan"/>
插件扩展:
插件拦截的场景:
场景一:修改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文件添加如下依赖:
com.github.pagehelper pagehelper 4.2.0
b. 在Mybatis配置xml中配置拦截器插件
在mybatis-config.xml文件中添加插件后的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
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 additionalParameters = (Map) 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整合进行批量操作
executorType" ref="BATCH">
java中使用:
@Autowired
private SqlSession sqlSession;
场景四:存储过程
场景五:typeHandler处理枚举
可以通过自定义TypeHandler的形式来在设置参数或取出结果集的时候自定义参数封装策略
步骤:
1. 实现TypeHandler接口或继承BaseTypeHandler
2. 使用@MappedTypes定义处理的java类型
3. 在自定义结果集标签或参数处理的时候声明使用自定义TypeHandler进行处理
或在全局配置TypeHandler要处理的javaType
多插件编写:
MyFirstPlug"> MySecondPlug">