大家好,我是考100分的小小码 ,祝大家学习进步,加薪顺利呀。今天说一说【mybatis】mybatis 拦截器工作原理源码解析[亲测有效],希望您对编程的造诣更进一步.
mybatis 拦截器工作原理(JDK动态代理)
1. mybatis 拦截器案例
场景:分页查询,类似成熟产品:pagehelper, 这里只做简单原理演示
1.0 mybatis全局配置 SqlMapConfig.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<typeAliases>
<package name="com.zhiwei.entity"/>
</typeAliases>
<plugins>
<plugin interceptor="com.zhiwei.advanced.plugin.MyInterceptor"/>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="advanced/StudentMapper.xml"/>
</mappers>
</configuration>
代码100分
1.1 StudentMapper.xml 映射器配置文件
代码100分<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhiwei.advanced.mapper.StudentMapper">
<resultMap type="com.zhiwei.entity.Student" id="studentResultMap">
<id column="sid" property="sid"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="gender" property="gender" javaType="java.lang.String"/>
<result column="score" property="score" javaType="java.lang.Integer"/>
<result column="address" property="address" typeHandler="com.zhiwei.advanced.type.AddressTypeHandler"/>
</resultMap>
<!-- 这里虽然查所有,但是默认Mybatis为性能考虑有默认限制:RowBounds: 0 - Integer.MAX_VALUE -->
<!-- mybatis默认配置:org.apache.ibatis.session.defaults.DefaultSqlSession#select(RowBounds.DEFAULT) -->
<select id="queryAll" resultMap="studentResultMap">
select * from student
</select>
</mapper>
1.2 StudentMapper: 映射器
public interface StudentMapper {
public List<Student> queryAll();
}
1.3 StudentService: 服务类
代码100分public static List<Student> queryByPage(Integer pageNow, Integer pageSize) {
PageHelper.startPage(pageNow, pageSize);
try{
return sqlSession.getMapper(StudentMapper.class).queryAll();
}finally{
sqlSession.close();
}
}
1.4 分页拦截器
1.4.1 分页参数 PageParam
package com.zhiwei.advanced.plugin;
import lombok.Data;
@Data
public class PageParam {
private Integer pageNow;
private Integer pageSize;
private Boolean startPageMark;
}
1.4.2 分页辅助类
package com.zhiwei.advanced.plugin;
public final class PageHelper {
// 通过ThreadLocal保存分页参数,相同线程实现分页参数共享
private static ThreadLocal<PageParam> threadPageParam = new ThreadLocal();
/**
* 开始分页
* @param pageNow
* @param pageSize
*/
public static void startPage(Integer pageNow, Integer pageSize){
PageParam pageParam = new PageParam();
pageParam.setPageNow((pageNow == null ? 1 : pageNow));
pageParam.setPageSize((pageSize == null ? 10 : pageSize));
pageParam.setStartPageMark(true);
threadPageParam.set(pageParam);
}
/**
* 获取分页参数
* @return
*/
public static PageParam getPageParam(){
try{
PageParam pageParam = threadPageParam.get();
}finally{
threadPageParam.remove(); // 清理本地线程缓存,防止内存泄漏
}
return pageParam;
}
}
1.4.3 分页SQL处理工具类
package com.zhiwei.advanced.plugin;
import java.util.HashMap;
import java.util.Map;
public final class PageSqlUtil {
private static final Map<String,String> pageFormatMap = new HashMap<>();
// 这里拿MYSQL做案例,PG分页参数用法不一样,需进一步细分处理
static{
pageFormatMap.put("MYSQL", " limit %d , %d");
}
public static String getPageSql(String databaseName, PageParam pageParam){
if (databaseName == null || !pageFormatMap.containsKey(databaseName.toUpperCase())){
throw new RuntimeException("插件暂不支持此数据库"+ databaseName +"类型");
}
return String.format(pageFormatMap.get(databaseName.toUpperCase()), (pageParam.getPageNow() - 1) * pageParam.getPageSize(), pageParam.getPageSize());
}
}
1.4.4 拦截器
package com.zhiwei.advanced.plugin;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
@Intercepts(
// 拦截StatementHandler的prepare方法,也就是生成PrepareStatement阶段,为后面PS的参数设置做准备
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
)
@Slf4j
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截对象:这里拦截StatementHandler,默认是RoutingStatementHandler装饰类,实际委托处理类:PreparedStatementHandler
if (invocation.getTarget() instanceof StatementHandler) {
// 当前工作线程未设置分页参数,则直接返回
PageParam pageParam = PageHelper.getPageParam();
if (null == pageParam || !pageParam.getStartPageMark()) {
return invocation.proceed();
}
//mybatis获取对象成员的方法:本质反射代理机制
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, new DefaultObjectFactory(),
new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
// 获取委托对象delegate(PreparedStatementHandler)的boundSql对象的sql成员:对应原生sql: 案例为:select * from student
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
// 重新设置经过处理后的sql数据:select * from student limit (pageParam.getPageNow() - 1) * pageParam.getPageSize(), pageParam.getPageSize()
metaStatementHandler.setValue("delegate.boundSql.sql", buildSql(getDatabaseInfo((Connection) invocation.getArgs()[0]), originalSql, pageParam));
}
return invocation.proceed();
}
private String getDatabaseInfo(Connection connection){
String databaseName = null;
try {
databaseName = connection.getMetaData().getDatabaseProductName();
} catch (SQLException e) {
e.printStackTrace();
}
log.info("连接数据库名称:{}", databaseName);
return databaseName;
}
/***
* 构建完成sql:
* 优化:根据不同的数据库产品配置不同的构建策略
* @param databaseName 数据库名称
* @param originalSql
* @param pageParam
* @return
*/
private String buildSql(String databaseName, String originalSql, PageParam pageParam){
if (pageParam == null){
return originalSql;
}
return originalSql + PageSqlUtil.getPageSql(databaseName, pageParam);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
1.4.5 测试
/**
* 拦截器分页查询
*/
@Test
public void queryByPage() {
List<Student> students = StudentService.queryByPage(2, 5);
log.info("students:{}", students);
}
2. Interceptor 工作原理
拦截四大对象:
- Executor(执行器,负责数据库业务执行整个流程): org.apache.ibatis.session.Configuration#newExecutor
- ParameterHandler(参数处理器:为Statement设置请求参数的值):org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
- StatementHandler(语句处理器:统一调度Statement生成过程,构成完整版的PrepareStatement):org.apache.ibatis.session.Configuration#newStatementHandler
- ResultSetHandler(处理数据库响应之后的记录,映射成自定义的实体类,方便处理):org.apache.ibatis.session.Configuration#newResultSetHandler: resultSetHandler
2.1 Interceptor 注册
全局配置文件SqlMapConfig.xml 配置Plugins标签,案例为:MyInterceptor
标签解析:org.apache.ibatis.builder.xml.XMLConfigBuilder.pluginElement
Interceptor维护:Configuration.interceptorChain
2.2 Interceptor 工作流程(案例:StatementHandler)
2.2.1 构造 StatementHandler
接口:org.apache.ibatis.session.Configuration.newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建StatementHandler装饰器:StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// mybatis 对拦截的对象进行处理
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
2.2.2 拦截器链工作流程
接口:org.apache.ibatis.plugin.InterceptorChain.pluginAll
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 拦截器拦截目标对象,本质返回JDK动态代理对象(StatementHandler的代理对象),如果存在多个拦截器则层层代理
target = interceptor.plugin(target);
}
return target;
}
2.2.3 拦截器动态代理逻辑
案例接口:com.zhiwei.advanced.plugin.MyInterceptor.plugin
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
代理对象生成逻辑
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
//signatureMap:保存拦截器拦截目标及方法
// getAllInterfaces: 判断目标对象是否是拦截器拦截的范围,根据目标对象类型确定
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 若目标对象在拦截器拦截范围则直接生成代理对象返回,否则直接返回原生对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 目标对象不在拦截器拦截范围,直接返回
// 例如:案例中拦截StatementHandler,若目标对象为ParameterHandler,则直接不处理原生返回
return target;
}
//判断目标对象是否在拦截器拦截范围
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
2.2.4 拦截器生成代理对象处理逻辑
接口:org.apache.ibatis.plugin.Plugin
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 目标对象执行方法在拦截器拦截范围,则直接拦截交给拦截器处理,否则直接执行
// 案例:StatementHandler拦截Prepare方法,但是执行parameterize并不在拦截范围,则跳过拦截器直接处理
if (methods != null && methods.contains(method)) {
// 案例对应:com.zhiwei.advanced.plugin.MyInterceptor.intercept, Invocation包含拦截的目标对象,执行方法、参数, 注意这里目标对象是StatementHandler装饰类:RoutingStatementHandler
return interceptor.intercept(new Invocation(target, method, args));
}
// 目标方法不在拦截范围,直接执行,相当于生成的动态代理类不作任何处理
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
…………………………………… mybatis Interceptor 工作原理分析完毕 …………………………………………
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/7525.html