MyBatis缓存原理及插件实现

news/2025/1/18 19:36:13/

目录

MyBatis缓存原理

缓存的工作机制

一级缓存

二级缓存

MyBatis插件实现


MyBatis缓存原理

缓存的工作机制

  • 如果会话查询了一条数据,此数据会存入一级缓存
  • 若会话被关闭或提交,则,其数据转存入二级缓存
  • 新会话若再次查询之前查询过的数据,就从二级缓存中获取;
  • 不同的Mapper,查询出来的数据会被放置到不同的二级缓存中。

一级缓存

  • SqlSession级别的,也称为本地缓存
  • 一级缓存的本质,就是:使用一个Map对象来存储SQL查询到的结果集[Java对象]
  • MyBatis默认开启一级缓存

一级缓存何时失效:

  • 会话不同;
  • 会话相同
  1. 查询条件不同
  2. 多次查询之间,进行了增删改操作 [或commit() | 清空缓存]
  3. clearCache() 手动情况缓存


二级缓存

  • 全局缓存,基于namespace,每一个namespace对应一个二级缓存
  • 二级缓存的数据来自于一级缓存
  • 二级缓存需要手动开启:

        ① 全局配置文件 <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应用中配置多个插件

插件原理
拦截器可以在四大对象的方法上装配插件:

  • Executor [实现了二级缓存的处理]
  • StatementHandler [实现了一级缓存的处理]
  • ParameterHandler
  • ResultSetHandler

因为四大对象在创建的过程中,都实现了拦截器链的处理,我们以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>


http://www.ppmy.cn/news/1564225.html

相关文章

Nacos: 一个动态服务发现与配置管理平台

Nacos: 一个动态服务发现与配置管理平台 引言 在微服务架构日益普及的今天&#xff0c;服务之间的调用和配置管理变得越来越复杂。为了简化这一过程并提高开发效率&#xff0c;阿里巴巴推出了Nacos——一个易于使用的动态服务发现、配置管理和服务管理平台。 Nacos是什么&am…

C#图表性能的巅峰之选:LightningChart®.NET

C#图表性能的巅峰之选&#xff1a;LightningChart.NET 引言 在现代数据可视化中&#xff0c;性能是选择图表控件的关键因素。无论是实时数据监控、海量数据分析还是复杂 3D 可视化&#xff0c;LightningChart.NET 都以其卓越的性能成为市场上的佼佼者。 本文将深入探讨 Ligh…

渗透笔记1

第一天 工具&#xff1a;cs cobalt strike 4.9 / msf kali &#xff08;自带 Ubuntu&#xff09; cs cobalt strike 4.9&#xff1a;server-client server部署在云服务器上&#xff0c;client分别在各地&#xff0c;与server相连接&#xff1b;连接上后就可以共享上线主机。…

IM聊天学习资源

文章目录 参考链接使用前端界面简单效果消息窗口平滑滚动至底部vue使用watch监听vuex中的变量变化 websocket握手认证ChatKeyCheckHandlerNettyChatServerNettyChatInitializer 参考链接 zzhua/netty-chat-web - 包括前后端 vue.js实现带表情评论功能前后端实现&#xff08;仿…

源码编译安装httpd 2.4,提供系统服务管理脚本并测试

1.安装httpd wget https://downloads.apache.org/httpd/httpd-2.4.62.tar.gzbmcv tar -zxvf httpd-2.4.62.tar.gz cd httpd-2.4.62 2.安装依赖包 sudo yum install -y gcc make apr-devel apr-util-devel pcre-devel sudo yum groupinstall "Development Tools"…

在 .NET 9 中使用 Scalar 替代 Swagger

前言 在.NET 9发布以后ASP.NET Core官方团队发布公告已经将Swashbuckle.AspNetCore&#xff08;一个为ASP.NET Core API提供Swagger工具的项目&#xff09;从ASP.NET Core Web API模板中移除&#xff0c;这意味着以后我们创建Web API项目的时候不会再自动生成Swagger API文档了…

关于SQL注入的面试题及经验分享(附视频教程)

在前两期的面试经验分享中&#xff0c;我们收到了小伙伴们的热烈反馈&#xff0c;大家对面试话题征集活动展现出了浓厚的兴趣&#xff0c;纷纷表示希望能够了解更多关于面试的技巧和经验。 《SQL注入从0到1》是一套完整的漏洞解析课程&#xff0c;本课程全面剖析SQL注入原理、…

Titans Learning to Memorize at Test Time

在语言建模任务上,拥有 760M 参数的 Titans(MAC) 在 WikiText 上达到了 19.93 的困惑度,显著优于同等规模的 Transformer++(25.21) 和 Mamba2(22.94)。在常识推理任务上,Titans 在包括 PIQA、HellaSwag、WinoGrande 等 9 个基准测试中的平均准确率达到 52.51%,超过了现…