Mybatis源码阅读

本文介绍了如何在Mybatis中使用动态代理为Mapper生成代理对象,详细阐述了JDK代理的实现方式以及插件机制在SQL执行过程中的应用。同时,对一级和二级缓存的原理、设置和数据一致性进行了总结。
摘要由CSDN通过智能技术生成

使用动态代理为Mapper生成代理对象

  • Mapper接口没有实现类,Mapper接口中定义的方法是如何执行的?
  • 模拟接口如何通过JDK动态代理进行方法调用
    Proxy.newProxyInstance(classLoader, interface, invocationHandler)
  
    能够创建一个工厂,提供一个方法用来根据接口动态生成代理对象?
    
    proxyFactory = new proxyFactory(Interface);
    proxy = proxyFactory.getProxy(invocationHandler);  mapperProxy就是invocationHandler的实现类

  • mybatis中具体实现
    getMapper()的执行流程:
    1. getMapper(type)以传入接口为例,以接口作为key在mapperRegistry注册表中获取对应的mapperProxyFactory[工厂对象]
    2. 传入调用处理器,通过工厂对象的newInstance方法创建代理对象,并转为接口类型 [底层调用JDK动态代理]
    3. 根据JDK动态代理的特性,代理对象调用接口方法最终是交给调用处理器invocationHandler.invoke()处理
    
    
    
    mapperProxy作为invocationHandler的实现类,并不进行实际的invoke操作,而是将具体操作进行转发(将接口和SQL解耦)
    通过传入mapperMethod构造mapperMethodInvoker对象,在mapperMethodInvoker中执行invoke方法,最终调用mapperMethod的execute方法
    
    看到这里,mapperProxy既是mybatis中mapper接口的调用处理器,同时也可以看成是Java动态代理的代理,在mapperProxy中做了相关的处理和转发工作
    
    

插件机制

  1. mybatis启动时,如果有插件,则会为handler和executor创建代理对象(取决于插件配置中的拦截类型)

pluginAll()就是为传入的对象创建代理对象,通过Proxy.newProxyInstance(),且调用处理器invocationHandler就是Plugin类的实例
最后返回代理对象

    public ParameterHandler newParameterHandler(MappedStatement mappedStatement,Object parameterObject,BoundSql boundSql){
        // ...    
        parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
    
    public ResultSetHandler newResultSetHandler(Executor executor,MappedStatement mappedStatement,RowBounds rowBounds,ParameterHandler parameterHandler,
        // ...    
        resultSetHandler=(ResultSetHandler)interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }
    
    public StatementHandler newStatementHandler(Executor executor,MappedStatement mappedStatement,Object parameterObject,RowBounds rowBounds,ResultHandler resultHandler,BoundSql boundSql){
        // ...    
        statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }
    public Executor newExecutor(Transaction transaction,ExecutorType executorType){
        // ...    
        executor=(Executor)interceptorChain.pluginAll(executor);
        return executor;
    }

注意:拦截不同接口的插件,会在不同的时机被加载触发。比如newParameterHandler()newStatementHandler等被调用时。
一条SQL被执行时,可以看做有一个生命周期,如:
语句处理 – 参数处理 – 执行 – 结果集处理
分别对应上述的四个方法,SQL在不同的阶段可以调用不同的插件进行细致化的处理(前提是配置了相应的插件)

  1. 代理对象执行

当方法执行到插件注解中配置的方法时,由于相应的handler或executor对象是代理对象,所以会调用代理对象的invoke(),即plugin的invoke()方法

在这里插入图片描述

      @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        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);
    }
}
  1. 插件的执行

在plugin的invoke()方法中,如果方法时拦截方法,则调用interceptor.intercept()方法,即插件逻辑,否则直接调用真实对象的方法
注意,此处的interceptor是在创建代理对象时就传入的
插件逻辑执行完成,需要调用真实对象的方法,即需要执行invocation.proceed()执行最终的SQL操作.
上面的过程不就是妥妥的代理模式么…

缓存

  1. 一级缓存:
    默认开启,作用域是一个sqlSession,即一个sqlSession内,相同查询不会再查询数据库
  2. 二级缓存:
    • 默认关闭,作用域是同一个Mapper接口,即同一个Mapper接口的所有方法共享一个缓存(前提是都开启了二级缓存)
    • 自定义缓存需实现序列化接口
    • 需进行全局配置开启和局部配置开启
    <!--全局配置-->
   <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <!--    Mapper文件配置-->
    <mapper namespace="com.example.YourMapper">
        <!-- 开启此Mapper下的所有select语句的二级缓存 -->
        <cache />
        
        <!-- 或者针对特定的select语句开启二级缓存 -->
        <select id="selectById" resultType="com.example.YourEntity" useCache="true">
            SELECT * FROM your_table WHERE id = #{id}
        </select>
        
        <!-- 可以进一步自定义缓存行为,例如指定eviction策略、flushInterval等 -->
        <cache eviction="FIFO" size="1024" flushInterval="60000" readOnly="true" />
    </mapper>
  1. 总结:
    • 一级缓存粒度细,针对一次sqlSession,二级缓存粒度大,一个Mapper接口的所有方法共享一个缓存
    • 增删改操作,mybatis会自动清空相关缓存,来保证数据一致性.
    • 二级缓存默认也是内存,支持自定义
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值