使用动态代理为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中做了相关的处理和转发工作
插件机制
- 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在不同的阶段可以调用不同的插件进行细致化的处理(前提是配置了相应的插件)
- 代理对象执行
当方法执行到插件注解中配置的方法时,由于相应的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);
}
}
- 插件的执行
在plugin的invoke()方法中,如果方法时拦截方法,则调用interceptor.intercept()方法,即
插件逻辑
,否则直接调用真实对象的方法
注意,此处的interceptor是在创建代理对象时就传入的
插件逻辑执行完成,需要调用真实对象的方法,即需要执行invocation.proceed()执行最终的SQL操作.
上面的过程不就是妥妥的代理模式么…
缓存
- 一级缓存:
默认开启,作用域是一个sqlSession,即一个sqlSession内,相同查询不会再查询数据库 - 二级缓存:
- 默认关闭,作用域是同一个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>
- 总结:
- 一级缓存粒度细,针对一次sqlSession,二级缓存粒度大,一个Mapper接口的所有方法共享一个缓存
- 增删改操作,mybatis会自动清空相关缓存,来保证数据一致性.
- 二级缓存默认也是内存,支持自定义