目录
MyBatis缓存原理
缓存的工作机制
一级缓存:
二级缓存:
MyBatis插件实现
MyBatis缓存原理
缓存的工作机制
- 如果会话查询了一条数据,此数据会存入一级缓存;
- 若会话被关闭或提交,则,其数据转存入二级缓存;
- 新会话若再次查询之前查询过的数据,就从二级缓存中获取;
- 不同的Mapper,查询出来的数据会被放置到不同的二级缓存中。
一级缓存:
一级缓存何时失效:
- 会话不同;
- 会话相同
二级缓存:
① 全局配置文件 <setting name="cacheEnabled" value="true"/>
② Mappere文件中启用缓存,进行配置 <cache></cache>
缓存的原理
- Cache接口 - MyBatis缓存的顶级接口
- MyBatis缓存 - 本质上就是一个Map对象 Map<Object, Object> keyMap
- PerpetualCache类 - 实现了Cache接口,一二级缓存都是通过PerpetualCache类来实现的
一级缓存,执行的是:BaseExecutor类的query()方法,来调用PerpetualCache类
二级缓存,执行的是:CachingExecutor类的query()方法,来调用PerpetualCache类
缓存的源码实现
缓存源码嵌入在SQL执行的过程中
MyBatis插件实现
插件[定义]源头
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, MappedStatementmappedStatement, 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, BoundSqlboundSql) {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 = newDefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 植入插件逻辑(返回代理对象) >>resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}// --InterceptorChain类--public class InterceptorChain {
// 保存所有的 Interceptor 即:所有的插件保存在interceptors这个List集合中private final List<Interceptor> 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<Class<?>,Set<Method>>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。*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{try {
// 获取被拦截方法列表 [获取当前方法所在类或接口中,可被当前Interceptor拦截的方法]Set<Method> 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中配置拦截器
<plugins>
<plugin interceptor="com.gupaoedu.interceptor.XxxInterceptor">
<property name="xxx" value="5"/>
</plugin>
</plugins>
拦截器在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;@Overridepublic 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;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic 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());}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.gupaoedu.vip.interceptor.FrancisInterceptor">
<property name="limitTime" value="10"/>
</plugin>
</plugins>