1、插件的应用场景
-
分页功能
mybatis的分页默认是基于内存分页的(查出所有,再截取),数据量大的情况下效率较低,不过使用mybatis插件可以改变该行为,只需要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句即可;例如:Mybatis-plus的分页插件。
-
公共字段统一赋值
一般业务系统都会有创建者,创建时间,修改者,修改时间四个字段,对于这四个字段的赋值,实际上可以在DAO层统一拦截处理,可以用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值即可;
-
性能监控
对于SQL语句执行的性能监控,可以通过拦截Executor类的update, query等方法,用日志记录每个方法执行的时间;
2、插件原理
mybatis在开启sqlSession到执行一次流程的过程给死个重要的对象设计了插件的功能,本质上去使用Jdk的动态代理来实现自定的开发。这四个对象分别是:
- 执行器Executor(update、query、commit、rollback等方法)
- 参数处理器ParameterHandler(getParameterObject、setParameters方法)
- 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法)
- SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法)
具体的原理在上一篇文章中简要的介绍过了。
首先在Plugin中包装成代理对象:
public static Object wrap(Object target, Interceptor interceptor) {
// 解析@Intercepts注解中需要拦截的方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取需要代理的接口,然后使用Jdk代理,Invoke对象就是Plugin本身
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
具体的执行代码是在Plugin的invoke方法中了,源码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 从缓存中获取需要拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 需要拦截则调用拦截器的intercept方法拦截
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
3、使用案例
基于我上一篇文章中的分析案例使用
package com.xiao7.mybatis.plugins;
import org.apache.ibatis.executor.Executor;
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.Method;
import java.util.Properties;
/**
* @author: xiao7
* @date: Created in 13:39 2021/5/18
* @description: Executor插件
* @version:
*/
@Intercepts({@Signature(type = Executor.class, method = "query",
args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
}
)})
public class ExecutorPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object target = invocation.getTarget();
Object[] args = invocation.getArgs();
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
测试类:
package com.xiao7.mybatis;
import com.xiao7.mybatis.entity.User;
import com.xiao7.mybatis.mapper.UserMapper;
import com.xiao7.mybatis.plugins.ExecutorPlugin;
import com.xiao7.mybatis.plugins.ParameterHandlerPlugin;
import com.xiao7.mybatis.plugins.ResultSetHandlerPlugin;
import com.xiao7.mybatis.plugins.StatementHandlerPlugin;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @author: xiao7
* @date: Created in 9:34 2021/5/18
* @description: mybatis测试类
* @version:
*/
public class MybatisExample {
public static final String URL = "jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8";
public static final String USER = "root";
public static final String PASSWORD = "123456";
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
//读取mybatis-config.xml
inputStream = Resources.getResourceAsStream(resource);
//解析mybatis-config.xml配置文件,创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSessionFactory.getConfiguration().addInterceptor(new ExecutorPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new ParameterHandlerPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new ResultSetHandlerPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new StatementHandlerPlugin());
//创建sqlSession
sqlSession = sqlSessionFactory.openSession(true);
//创建userMapper对象(UserMapper并没有实现类)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper对象的方法
User user = userMapper.selectById("1");
System.out.println(user);
// 提交
sqlSession.commit();
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectById("1");
System.out.println(user1);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
sqlSession.close();
}
}
}
插件开发需要注意的是:
- 不要定义过多的插件,代理嵌套过多,执行方法的时候,比较耗性能;
- 拦截器实现类的intercept方法里最后不要忘了执行invocation.proceed()方法,否则多个拦截器情况下,执行链条会断掉;