MyBatis缓存原理及插件实现

目录

MyBatis缓存原理

缓存的工作机制

一级缓存:

二级缓存:

MyBatis插件实现


MyBatis缓存原理

MyBatis缓存原理及插件实现_第1张图片

缓存的工作机制

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

一级缓存:

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

一级缓存何时失效:

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


二级缓存:

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

        ① 全局配置文件
        ② Mappere文件中启用缓存,进行配置
缓存的原理

  • Cache接口 - MyBatis缓存的顶级接口
  • MyBatis缓存 - 本质上就是一个Map对象 Map 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, 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());
    }
}



        
                
        

你可能感兴趣的:(mybatis,缓存,spring)