Mybatis插件原理

本文详细介绍了MyBatis拦截器的使用,包括Executor和StatementHandler的拦截实现,展示了如何通过拦截器修改SQL语句和处理数据。同时,文章深入解析了拦截器的JDK动态代理原理,解释了为何拦截器能作用于四大对象,并概述了Executor的执行流程。最后,总结了涉及的设计模式,如模板模式、装饰者模式等。
摘要由CSDN通过智能技术生成

一、插件的使用

2. 介绍

        MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的对象方法调用包括:

Executor:MyBatis 执行器,负责 SQL 语句的生成和查询缓存的维护,继承关系如下:

StatementHandler:封装了JDBC Statement 操作,继承关系如下:

 

ParameterHandler:负责SQL参数的组装

ResultSetHandler:负责SQL结果的组装

2. 实现

(1)定义拦截器

StatementHandler拦截的实现:

@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
@Slf4j
public class MybatisInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable{
		log.info("拦截器内部处理");
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target){
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties){
    }
}

 Executor拦截的实现:

import com.example.demo.mybatis.domain.BasePO;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
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 org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Properties;


@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
        , @Signature(type = Executor.class, method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
        , @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
@Component
//@Order(10)
public class MybatisInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable{

        // 执行器
        Executor executor = (Executor)invocation.getTarget();

        // 方法参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement)args[0];
        Object parameter = args[1];

        switch (ms.getSqlCommandType()){
            case SELECT:
                RowBounds rowBounds = (RowBounds)args[2];
                ResultHandler resultHandler = (ResultHandler)args[3];
                CacheKey cacheKey;
                BoundSql boundSql;
                if (args.length == 4) {
                    boundSql = ms.getBoundSql(parameter);
                    cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
                } else {
                    cacheKey = (CacheKey)args[4];
                    boundSql = (BoundSql)args[5];
                }
                String originSql = boundSql.getSql();
                log.info("【MybatisInterceptor】原始SQL: {}", originSql);
                String newSql = originSql + " order by id desc";

                SqlSource newSqlSource = this.buildSqlSource(boundSql, newSql);

//                MetaObject msObject = MetaObject.forObject(ms, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
//                msObject.setValue("sqlSource", newSqlSource);
//                log.info("【MybatisInterceptor】增强后SQL: {}", ms.getBoundSql(parameter).getSql());

                MappedStatement newMs = this.buildMappedStatement(ms, newSqlSource);
                log.info("【MybatisInterceptor】增强后SQL: {}", newMs.getBoundSql(parameter).getSql());
                args[0] = newMs;
                break;
            case INSERT:
                if(parameter instanceof BasePO){
                    ReflectUtil.setSuperValue("createTime", LocalDateTime.now(), parameter);
                }
            case UPDATE:
                if(parameter instanceof BasePO){
                    ReflectUtil.setSuperValue("updateTime", LocalDateTime.now(), parameter);
                }
        }
        return invocation.proceed();
    }

    /**
     * 新建一个 MappedStatement
     * 避免修改 Configuration中的 mappedStatements
     * 导致从自定义的 SqlSource 中获取增强后的SQL
     * getSqlBoundSql -> sqlSource.getBoundSql -> XML原始SQL
     * @param ms
     * @param sqlSource
     * @return
     */
    private MappedStatement buildMappedStatement(MappedStatement ms, SqlSource sqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), sqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource())
                .parameterMap(ms.getParameterMap())
                .resultMaps(ms.getResultMaps())
                .fetchSize(ms.getFetchSize())
                .timeout(ms.getTimeout())
                .statementType(ms.getStatementType())
                .resultSetType(ms.getResultSetType())
                .cache(ms.getCache())
                .flushCacheRequired(ms.isFlushCacheRequired())
                .useCache(ms.isUseCache())
                .resultOrdered(ms.isResultOrdered())
                .keyGenerator(ms.getKeyGenerator())
                .keyProperty(Objects.isNull(ms.getKeyProperties())?null:String.join(",", ms.getKeyProperties()));

        return builder.build();
    }

    private BoundSqlSqlSource buildSqlSource(BoundSql boundSql, String sql){
        // Mybatis 反射工具
        MetaObject msObject = MetaObject.forObject(boundSql, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        msObject.setValue("sql", sql);
        return new BoundSqlSqlSource(boundSql);
    }

    /**
     * 自定义内部类,利用反射赋值boundSql
     * boundSql 使用原MapperStatement不受影响
     * getSqlBoundSql -> sqlSource.getBoundSql 会从 StaticSqlSource 中获取XML原始SQL
     */
    static class BoundSqlSqlSource implements SqlSource {

        private BoundSql boundSql;

        BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

    @Override
    public Object plugin(Object target){
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties){

    }

}

(2)配置拦截器

  • Mybatis配置文件中增加插件配置
<?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>

    <plugins>
        <plugin interceptor="com.example.demo.mybatis.interceptor.MybatisInterceptor"/>
    </plugins>

</configuration>
  •  Spring中添加Mybatis插件
@Configuration
@MapperScan({"com.example.demo.mybatis.mapper"})
public class MyBatisConfig {

    @Resource
    private DataSource dataSource;

    @Resource
    private MybatisInterceptor mybatisInterceptor;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*Mapper.xml"));
        sqlSessionFactoryBean.setPlugins(new Interceptor[] {mybatisInterceptor});
        return sqlSessionFactoryBean.getObject();
    }

二、原理

1. JDK动态代理

        定义代理类实现InvocationHandler,持有被代理类的引用,重写invoke方法获取代理类实例的方法:

Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

        这里简单介绍jdk的动态代理,因为mybatis插件的实现原理基于此,只有在完全理解动态代理的基础上才能更好的理解mybatis插件的实现原理

2. 源码解析

(1)Interceptor

// 用于覆盖被拦截对象的原有方法(在调用代理对象 Plugin 的 invoke()方法时被调用) 
Object intercept(Invocation invocation) throws Throwable; 

// target 是被拦截对象,这个方法的作用是给被拦截对象生成一个代理对象,并返回
Object plugin(Object target); 

// 设置参数 
void setProperties(Properties properties);

(2)Plugin

// 基于动态代理实现
public class Plugin implements InvocationHandler{

    // 生成代理类
public static Object wrap(Object target, Interceptor interceptor) {
......
      return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
    }

    // 重写invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
      return interceptor.intercept(new Invocation(target, method, args));
}    
}

(3)Invocation

// 被代理对象
private final Object target;

private final Method method;
private final Object[] args;

// 反射执行被代理的方法
Public Object proceed() throws InvocationTargetException, IllegalAccessException {
  return method.invoke(target, args);
}

3. 执行流程

(1)Mapper动态代理

public class MapperProxy<T> implements InvocationHandler, Serializable {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ......
    return mapperMethod.execute(sqlSession, args);
  }
}

 这里是基于JDK动态代理实现调用mapper接口,最终实现由代理类执行XML中SQL.。承上启下,不做详细介绍,感兴趣的小伙伴可以另行查阅,或者留言有机会我来总结一下一起学习。如下执行流程Mapper -> MapperMethod -> SqlSession -> Executor,自此便到了我们拦截器可拦截器的对象

(2)四大代理对象

Open session时创建executor

Configuration

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    ......
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

执行SQL时创建StatementHandler

SimpleExecutor

public <E> List<E> doQuery(......) throws SQLException {
......
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  }


Configuration

public StatementHandler newStatementHandler(......) {
   StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

创建StatementHandler时创建ParameterHandler和ResultSetHandler

BaseStatementHandler

protected BaseStatementHandler(......) {
this.parameterHandler=configuration.newParameterHandler(mappedStatement,parameterObject,boundSql);
    this.resultSetHandler=configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }


Configuration

public ParameterHandler newParameterHandler(......) {
parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

public ResultSetHandler newResultSetHandler(......) {
resultSetHandler=(ResultSetHandler)interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

通过以上源码,解释了为什么拦截器会对该四大对象生效 

(3)Executor拦截器

Executor拦截器的执行流程:

  1. 执行query时,由于动态代理,执行Plugin的invoke方法
  2. 继续执行Plugin invoke方法中的interceptor.intercept,进入自定义的拦截器,执行invocation.proceed()
  3. 执行invocation.proceed(),通过代理执行被代理类query方法

4. 设计模式总结

Executor:模板模式、装饰者模式

InterceptorChain:责任链模式

StatementHandler:委派模式、策略模式

Plugin:代理模式

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值