【mybatis】mybatis拦截器工作原理源码解析

database

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>

1.1 StudentMapper.xml 映射器配置文件

<?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: 服务类

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 工作原理分析完毕 ................................................

以上是 【mybatis】mybatis拦截器工作原理源码解析 的全部内容, 来源链接: utcz.com/z/534475.html

回到顶部