插件原理没有搞懂, 不要进行插件的开发, 容易导致很严重的问题
插件是用来改变或者扩展mybatis的原有的功能,mybaits的插件就是通过继承Interceptor拦截器实现的;在没有完全理解插件之前禁止使用插件对mybaits进行扩展,又可能会导致严重的问题;
mybatis中能使用插件进行拦截的接口和方法如下:
定义一个阈值,当查询操作运行时间超过这个阈值记录日志供运维人员定位慢查询,插件实现步骤:
1.实现Interceptor接口方法
2.确定拦截的签名
3.在配置文件中配置插件
4.运行测试用例
org.apache.ibatis.plugin.Interceptor
public interface Interceptor {
//执行拦截逻辑的方法
Object intercept(Invocation invocation) throws Throwable;
//target是被拦截的对象,它的作用就是给被拦截的对象生成一个代理对象
Object plugin(Object target);
//读取在plugin中设置的参数
void setProperties(Properties properties);
}
@Intercepts(
{@Signature(type = StatementHandler.class,
method = "query",
args = {Statement.class, ResultHandler.class})
// @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
public class ThresholdInterceptor implements Interceptor {
private long threshold;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.currentTimeMillis();
Object ret = invocation.proceed();
long end = System.currentTimeMillis();
long runTime = end - begin;
if (runTime >= threshold) {
Object[] args = invocation.getArgs();
Statement stat = (Statement)args[0];
MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
PreparedStatementLogger statementLogger =
(PreparedStatementLogger)metaObjectStat.getValue("h");
Statement statement = statementLogger.getPreparedStatement();
System.out.println("sql语句:“" + statement.toString() + "”执行时间为:"
+ runTime + "毫秒,已经超过阈值!");
}
return ret;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 封装
}
@Override
public void setProperties(Properties properties) { // 读取参数配置
this.threshold = Long.valueOf(properties.getProperty("threshold"));
}
}
<plugins>
<plugin interceptor="com.len.mybatis.Interceptors.ThresholdInterceptor">
<property name="threshold" value="10"/>
plugin>
plugins>
责任链模式:就是把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作;和装饰器模式不同,每个节点都知道后继者是谁;适合为完成同一个请求需要多个处理类的场景;
要素分析
责任链模式优点:
参考连接
Mybatis-PageHelper/Interceptor.md at master · pagehelper/Mybatis-PageHelper · GitHub
1.插件的初始化 (XMLConfigBuilder.pluginElement)
2.插件的加载 (Configuration.new*方法,四大对象的创建)
3.插件的调用 (Plugin. wrap、 Plugin. invoke)
public class Configuration {
/*插件集合*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
public List<Interceptor> getInterceptors() {
return interceptorChain.getInterceptors();
}
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
}
public class InterceptorChain {
// 使用list数据结构, 所以责任链的顺序就是配置的顺序
private final List<Interceptor> interceptors = new ArrayList<>();
}
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果有节点,通过装饰器,添加二级缓存的能力
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
executor = (Executor)interceptorChain.pluginAll(executor);
return executor;
}
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
插件什么时候被代理的?
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
try {
...
//解析节点
pluginElement(root.evalNode("plugins"));
...
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "
+ e, e);
}
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//遍历所有的插件配置
for (XNode child : parent.getChildren()) {
//获取插件的类名
String interceptor = child.getStringAttribute("interceptor");
//获取插件的配置
Properties properties = child.getChildrenAsProperties();
//实例化插件对象
Interceptor interceptorInstance = (Interceptor)resolveClass(interceptor).newInstance();
//设置插件属性
interceptorInstance.setProperties(properties);
//将插件添加到configuration对象,底层使用list保存所有的插件并记录顺序
configuration.addInterceptor(interceptorInstance);
}
}
}
这里并没有生成代理, 说明在解析plugins元素时候并没有生成代理, 那么是在使用时候被代理了
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
...
//通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
executor = (Executor)interceptorChain.pluginAll(executor);
return executor;
}
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target); // 重点是这个代码
}
return target;
}
}
com.len.mybatis.Interceptors.ThresholdInterceptor
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 使用了mybatis提供的工具生成代理
}
所以这个代理生成方式就是, 具体实现的方法需要提供代理生成的方法plugin, mybatis在使用插件时候会通过责任链的pluginAll方法回调这个plugin生成代理
分页插件的使用;
中文文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
使用手册:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
分页插件的注意事项;
注意事项:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Important.md
使用
依赖
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.1.4version>
dependency>
配置
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="pageSizeZero" value="true"/>
plugin>
plugins>
@Test
public void testManyParamQuery() {
// 2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
String email = "qq.com";
Byte sex = 1;
// 第一种方式使用map
// Map params = new HashMap();
// params.put("email", email);
// params.put("sex", sex);
// List list1 = mapper.selectByEmailAndSex1(params);
// System.out.println(list1.size());
// 第二种方式直接使用参数
Page<TUser> startPage = PageHelper.startPage(2, 3);
List<TUser> list2 = mapper.selectByEmailAndSex2(email, sex);
System.out.println(list2.size());
// return startPage;
// 第三种方式用对象
// EmailSexBean esb = new EmailSexBean();
// esb.setEmail(email);
// esb.setSex(sex);
// List list3 = mapper.selectByEmailAndSex3(esb);
// System.out.println(list3.size());
}
注意事项:
Page startPage = PageHelper.startPage(2, 3);
List list2 = mapper.selectByEmailAndSex2(email, sex);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4nDak4T-1659111130373)(C:/Users/hgy/AppData/Roaming/Typora/typora-user-images/image-20220704005549259.png)]