【mybatis】mybatis 拦截器工作原理源码解析[亲测有效]

【mybatis】mybatis 拦截器工作原理源码解析[亲测有效]mybatis 拦截器工作原理(JDK动态代理) 1. mybatis 拦截器案例 场景:分页查询,类似成熟产品:pagehelper, 这里只做简单原理演示 1.0 mybatis全局配置 Sql…

【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);
}

【mybatis】mybatis 拦截器工作原理源码解析[亲测有效]

【mybatis】mybatis 拦截器工作原理源码解析[亲测有效]

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

(0)
上一篇 2023-03-18
下一篇 2023-03-19

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注