目录
MyBatis缓存原理
缓存的工作机制
一级缓存:
二级缓存:
MyBatis插件实现
一级缓存何时失效:
① 全局配置文件
② Mappere文件中启用缓存,进行配置
缓存的原理
一级缓存,执行的是:BaseExecutor类的query()方法,来调用PerpetualCache类
二级缓存,执行的是:CachingExecutor类的query()方法,来调用PerpetualCache类
缓存的源码实现
缓存源码嵌入在SQL执行的过程中
插件[定义]源头
MyBatis插件是以拦截器的方式植入整体逻辑的,Configuration类中存在属性interceptorChain - 拦截器链,这意味着我们可以在MyBatis应用中配置多个插件
插件原理
拦截器可以在四大对象的方法上装配插件:
因为四大对象在创建的过程中,都实现了拦截器链的处理,我们以StatementHandler为例,遍历它的创建过程,来窥视插件原理
在BaseStatementHandler构造器尾部,调用了configuration对象的方法:newParameterHandler()、newResultSetHandler()创建了parameterHandler对象和resultSetHandler对象;
进入newXxxHandler() 方法,观察:
每个创建出来的XxHandler对象,并不是直接返回,而是执行了interceptorChain.pluginAll()方法包装之后,才返回;
在pluginAll()方法中,获取到了所有的Interceptor
Interceptor就是 插件需要实现的接口
Interceptor翻译过来就是拦截器,也说明它是基于拦截器原理实现的调用interceptor.plugin(target) 方法,返回target经过包装后的对象
protected BaseStatementHandler(Executor executor, MappedStatement
mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) {
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 创建了四大对象中的两大对象 >>
// 创建这两大对象的时候分别做了什么?
this.parameterHandler = configuration.newParameterHandler
(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler
(executor, mappedStatement, rowBounds, parameterHandler,
resultHandler, boundSql);
}
// --Configuration类--
public ParameterHandler newParameterHandler
(MappedStatement mappedStatement, Object parameterObject, BoundSql
boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);// 植入插件逻辑(返回代理对象) >>
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler
(Executor executor, MappedStatement mappedStatement,
RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new
DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 植入插件逻辑(返回代理对象) >>
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
// --InterceptorChain类--public class InterceptorChain {
// 保存所有的 Interceptor 即:所有的插件保存在interceptors这个List集合中private final List interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
// 遍历拦截器链中的所有拦截器
for (Interceptor interceptor : interceptors) {
// 调用拦截器的 plugin 方法植入相应的插件逻辑 [创建对应的拦截器的代理对象]
// 观察方法:interceptor.plugin(target);
// interceptor - 自己编写的插件类 [实现Interceptor接口]
// plugin(target) - 重写plugin(target)方法 为参数target创建代理对象
// 进入plugin()方法 >>
target = interceptor.plugin(target);
}
return target;
}
}
// --Interceptor接口--
// 决定是否触发 intercept()方法
// plugin 方法在内部调用了 Plugin 类的 wrap 方法,用于为目标对象生成代理。
default Object plugin(Object target){
// 进入wrap()方法 >>
return Plugin.wrap(target,this);
}
// --Plugin类--public static Object wrap(Object target, Interceptor interceptor) {
// 获取用户自定义 Interceptor 中 @Signature 注解的信息
// getSignatureMap 负责处理@Signature 注解
Map,Set>signatureMap=getSignatureMap(interceptor);
// 获取目标类型
Class> type=target.getClass();// 获取目标类型实现了的 所有接口
Class>[]interfaces=getAllInterfaces(type,signatureMap);// 如果目标类型有实现的接口 就创建代理对象
if(interfaces.length>0){
// 通过 JDK 动态代理为目标类生成代理类return Proxy.newProxyInstance(
type.getClassLoader(),interfaces,
new Plugin(target,interceptor,signatureMap));
}
// 否则原封不动的返回目标对象
return target;
}
抬头再看Plugin类,其实现了 InvocationHandler 接口,所以它的 invoke 方法会拦截 装配了插件的对象所有的方法调用;
这一点,和我们上次课讲到的Mapper对象数据操作方法会执行MapperProxy类的invoke方法一样;invoke方法会对所拦截的方法进行检测,以决定是否执行插件逻辑,当代理对象方法被调用时,执行invoke() 方法。
/**
* 整体步骤:
* invoke 方法会检测被拦截方法是否配置在插件的 @Signature 注解中,* 若是,则执行插件逻辑;
* 否,则执行被拦截方法。
* 插件逻辑封装在 intercept 方法中,该方法的参数类型为 Invocation。*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
try {
// 获取被拦截方法列表 [获取当前方法所在类或接口中,可被当前Interceptor拦截的方法]
Set methods = signatureMap.get(method.getDeclaringClass());// 检测方法列表是否包含被拦截的方法
if (methods != null && methods.contains(method)) {
// 执行插件逻辑 [拦截操作 - 当前调用的方法需要被拦截]
// 只有通过Intercepts注解指定的方法才会执行我们自定义插件的intercept方法。未通过
Intercepts注解指定的将不会执行我们的intercept方法
// intercept() >>
return interceptor.intercept(new Invocation(target, method, args));
}
// 不需要拦截 则调用 目标对象中的方法[被拦截方法]
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
// --Interceptor接口--// 执行拦截逻辑的方法
// 由于我们在全局配置文件中装配了第三方分页插件 所以流程来到了PageHelper类
Object intercept(Invocation invocation) throws Throwable;
// --PageHelper类--public Object intercept(Invocation invocation) throws Throwable {
// ...
}
自定义插件
三步完成自定义插件的使用
① 定义一个类实现Interceptor接口
重写Interceptor提供的三个重载方法
intercept(Invocation invocation) 方法 - 执行拦截逻辑plugin(Object target) 方法 - 为参数目标对象生成代理
setProperties(Properties properties) 方法 - 初始化Intercept对象
若存在自己的业务逻辑,则提供对应的方法实现即可
② 在类上修饰注解
@Intercepts({@Signature(
type = XxxHandler.class,
method = "query",
args = {Statement.class, ResultHandler.class}
)})
③ 在mybatis-config.xml中配置拦截器
拦截器在XMLConfigBuilder类的pluginElement() 方法中被添加到Configuration中 [全局配置文件解析时]
自定义一个打印慢查询sql的插件
// --FrancisInterceptor--// @Description: 自定义一个慢查询统计插件,可以定义多个@Signature注解,因为@Intercepts支持数组,也就是
说可以同时拦截多个方法@Intercepts({
@Signature(
type = StatementHandler.class,method = "query",args = {Statement.class, ResultHandler.class}
)})
public class FrancisInterceptor implements Interceptor {
// 时长限制,超过这个时间则认为查询较慢
private long limitTime;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
Object proceed = invocation.proceed();
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;// 假设查询时间大于10毫秒,则认为查询较慢,打印日志
if (costTime > limitTime) {
BoundSql boundSql = statementHandler.getBoundSql();
printFormattedSql(boundSql, costTime);
}
return proceed;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
limitTime = Long.parseLong((String) properties.get("limitTime"));
}
/**
* 格式化打印sql
*
* @param boundSql
* @param millis
*/
private void printFormattedSql(BoundSql boundSql, long millis) {
StringBuilder sb = new StringBuilder();
sb.append("执行较慢的sql为:").append(boundSql.getSql()).append("\n参数值分别为:");
sb.append("\n耗时为:").append(millis).append("ms");
System.out.println(sb.toString());
}
}