Mybatis源码(十):Mybatis插件机制

1、Mybatis插件支持拦截的对象

  MyBatis 允许使用插件来拦截的方法调用,可在映射语句执行流程中进行拦截调用。Mybatis插件支持拦截的对象:

1、Executor:执行器

  Executor执行SQL的增删改查操作。

  Mybatis中对Executor做插件拦截的位置,Configuration#newExecutor()核心代码:

 1 // 创建执行器
 2 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
 3   executorType = executorType == null ? defaultExecutorType : executorType;
 4   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
 5   Executor executor;
 6   // 根据参数,选择合适的Executor实现
 7   if (ExecutorType.BATCH == executorType) {
 8     executor = new BatchExecutor(this, transaction);
 9   } else if (ExecutorType.REUSE == executorType) {
10     executor = new ReuseExecutor(this, transaction);
11   } else {
12     executor = new SimpleExecutor(this, transaction);
13   }
14   // 根据配置决定是否开启二级缓存的功能
15   if (cacheEnabled) {
16     executor = new CachingExecutor(executor);
17   }
18   // 此处调用插件,通过插件可以改变Executor行为
19   executor = (Executor) interceptorChain.pluginAll(executor);
20   return executor;
21 }

2、StatementHandler:数据库处理对象

  StatementHandler是JDBC的封装,用于创建Statement对象及映射1SQL语句中的占位符处理。

  Mybatis中对StatementHandler做插件拦截的位置,Configuration#newStatementHandler()核心代码:

1 //创建语句处理器
2 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
3   //创建路由选择语句处理器,根据statementType创建不同类型的StatementHandler
4   StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
5   //  插件插入
6   statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
7   return statementHandler;
8 }

3、ParameterHandler:参数处理器

  ParameterHandler处理映射SQL的参数对象。

  Mybatis中对ParameterHandler做插件拦截的位置,Configuration#newParameterHandler()核心代码:

1 // 创建参数处理器
2 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
3   // 创建ParameterHandler
4   ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
5   // 插件插入
6   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
7   return parameterHandler;
8 }

4、ResultSetHandler:结果集处理器

  ResultSetHandler:处理SQL的返回结果集。

  Mybatis中对ResultSetHandler做插件拦截的位置,Configuration#newResultSetHandler()核心代码:

1 // 创建结果集处理器
2 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
3     ResultHandler resultHandler, BoundSql boundSql) {
4   //创建DefaultResultSetHandler
5   ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
6   resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
7   return resultSetHandler;
8 }

2、自定义插件拦截器

  Mybtais中提供了插件示例ExamplePlugin,参照示例完成自定义插件的实现。

1、自定义插件

 1 import org.apache.ibatis.executor.CachingExecutor;
 2 import org.apache.ibatis.executor.Executor;
 3 import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
 4 import org.apache.ibatis.executor.resultset.ResultSetHandler;
 5 import org.apache.ibatis.mapping.MappedStatement;
 6 import org.apache.ibatis.plugin.*;
 7 import org.apache.ibatis.reflection.MetaClass;
 8 import org.apache.ibatis.reflection.MetaObject;
 9 import org.apache.ibatis.reflection.Reflector;
10 import org.apache.ibatis.session.ResultHandler;
11 import org.apache.ibatis.session.RowBounds;
12 import org.apache.ibatis.transaction.Transaction;
13 
14 import java.lang.reflect.Method;
15 import java.sql.ResultSet;
16 import java.sql.Statement;
17 import java.util.Arrays;
18 import java.util.Properties;
19 
20 /**
21  * @Description: 自定义插件
22  */
23 @Intercepts({
24         // 拦截Executor的query方法
25         @Signature(
26             type= Executor.class,
27             method = "query",
28             args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }
29         ),
30         // 拦截ResultSetHandler的handleResultSets方法
31         @Signature(
32                 type= ResultSetHandler.class,
33                 method = "handleResultSets",
34                 args = { Statement.class }
35         ),
36 })
37 public class SnailsPlugin implements Interceptor {
38 
39     @Override
40     public Object intercept(Invocation invocation) throws Throwable {
41         System.out.printf("intercept target ===> %s %n", invocation.getTarget());
42         System.out.printf("intercept method ===> %s %n", invocation.getMethod());
43         System.out.printf("intercept args ===> %s %n", invocation.getArgs());
44         if (invocation.getTarget() instanceof CachingExecutor) {
45             CachingExecutor executor = (CachingExecutor) invocation.getTarget();
46             Method[] methods = executor.getClass().getMethods();
47             for (Method method : methods) {
48                 System.out.printf("缓存执行器方法: %s %n", method.getName());
49             }
50         } else if (invocation.getTarget() instanceof DefaultResultSetHandler) {
51             DefaultResultSetHandler resultSetHandler =  (DefaultResultSetHandler) invocation.getTarget();
52             Class<? extends DefaultResultSetHandler> aClass = resultSetHandler.getClass();
53             Method[] methods = aClass.getMethods();
54             for (Method method : methods) {
55                 System.out.printf("结果集处理器方法: %s %n", method.getName());
56             }
57 
58 
59         }
60         return invocation.proceed();
61     }
62 
63     @Override
64     public Object plugin(Object target) {
65         System.out.printf("SnailsPlugin plugin ===> %s %n", target);
66         return  Plugin.wrap(target, this);
67     }
68 
69     @Override
70     public void setProperties(Properties properties) {
71         System.out.printf("SnailsPlugin setProperties ===> %s %n", properties);
72     }
73 }

2、插件添加到全局配置文件

  在Mybatis全局配置文件中,添加自定义插件

<plugins>
    <plugin interceptor="org.apache.ibatis.debug.plugin.SnailsPlugin">
        <property name="someProperty" value="100"/>
    </plugin>
</plugins>

3、执行查询

 1 public static void main(String[] args) throws IOException {
 2     String resource = "mybatis-config.xml";
 3     // 加载mybatis的配置文件
 4     InputStream inputStream = Resources.getResourceAsStream(resource);
 5     // 获取sqlSessionFactory
 6     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 7     // 获取sqlSession
 8     SqlSession sqlSession = sqlSessionFactory.openSession();
 9     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
10     // 查询用户
11     User user02 = mapper.selectUserById("101");
12     System.out.println("user02: " + user02);
13 }

4、执行结果

 1 SnailsPlugin plugin ===> org.apache.ibatis.executor.CachingExecutor@3ada9e37 
 2 intercept target ===> org.apache.ibatis.executor.CachingExecutor@3ada9e37 
 3 intercept method ===> public abstract java.util.List org.apache.ibatis.executor.Executor.query(org.apache.ibatis.mapping.MappedStatement,java.lang.Object,org.apache.ibatis.session.RowBounds,org.apache.ibatis.session.ResultHandler) throws java.sql.SQLException 
 4 intercept args ===> org.apache.ibatis.mapping.MappedStatement@4fccd51b 
 5 缓存执行器方法: update 
 6 缓存执行器方法: close 
 7 缓存执行器方法: query 
 8 缓存执行器方法: query 
 9 缓存执行器方法: isClosed 
10 缓存执行器方法: getTransaction 
11 缓存执行器方法: queryCursor 
12 缓存执行器方法: createCacheKey 
13 缓存执行器方法: deferLoad 
14 缓存执行器方法: isCached 
15 缓存执行器方法: flushStatements 
16 缓存执行器方法: commit 
17 缓存执行器方法: rollback 
18 缓存执行器方法: clearLocalCache 
19 缓存执行器方法: setExecutorWrapper 
20 缓存执行器方法: wait 
21 缓存执行器方法: wait 
22 缓存执行器方法: wait 
23 缓存执行器方法: equals 
24 缓存执行器方法: toString 
25 缓存执行器方法: hashCode 
26 缓存执行器方法: getClass 
27 缓存执行器方法: notify 
28 缓存执行器方法: notifyAll 
29 SnailsPlugin plugin ===> org.apache.ibatis.scripting.defaults.DefaultParameterHandler@60215eee 
30 SnailsPlugin plugin ===> org.apache.ibatis.executor.resultset.DefaultResultSetHandler@65e579dc 
31 SnailsPlugin plugin ===> org.apache.ibatis.executor.statement.RoutingStatementHandler@b065c63 
32 intercept target ===> org.apache.ibatis.executor.resultset.DefaultResultSetHandler@65e579dc 
33 intercept method ===> public abstract java.util.List org.apache.ibatis.executor.resultset.ResultSetHandler.handleResultSets(java.sql.Statement) throws java.sql.SQLException 
34 intercept args ===> org.apache.ibatis.logging.jdbc.PreparedStatementLogger@6be46e8f 
35 结果集处理器方法: handleResultSets 
36 结果集处理器方法: handleOutputParameters 
37 结果集处理器方法: handleCursorResultSets 
38 结果集处理器方法: handleRowValues 
39 结果集处理器方法: resolveDiscriminatedResultMap 
40 结果集处理器方法: wait 
41 结果集处理器方法: wait 
42 结果集处理器方法: wait 
43 结果集处理器方法: equals 
44 结果集处理器方法: toString 
45 结果集处理器方法: hashCode 
46 结果集处理器方法: getClass 
47 结果集处理器方法: notify 
48 结果集处理器方法: notifyAll 
49 user02: User{id=101, name='zs', createDate=Sat Dec 03 12:17:36 CST 2022, updateDate=Sat Dec 03 12:17:36 CST 2022}

3、分页插件PageHelper

1、引入分页插件maven依赖

<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.0.0</version>
</dependency>

2、分页插件添加到全局配置中

1 <plugins>
2     <!-- 分页插件 -->
3     <plugin interceptor="com.github.pagehelper.PageInterceptor">
4     </plugin>
5 </plugins>

3、查询验证

 1 public static void main(String[] args) throws IOException {
 2     String resource = "mybatis-config.xml";
 3     // 加载mybatis的配置文件
 4     InputStream inputStream = Resources.getResourceAsStream(resource);
 5     // 获取sqlSessionFactory
 6     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 7     // 获取sqlSession
 8     SqlSession sqlSession = sqlSessionFactory.openSession();
 9     // 获取Mapper接口代理对象
10     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
11     // 设置分页
12     Page<User> pages = PageHelper.startPage(1, 2);
13     // 查询用户
14     List<User> users = mapper.getUserList();
15     // 处处结果
16     users.forEach(System.out::println);
17     System.out.printf("pages info : %s %n", pages);
18 }

  输出结果

User{id=101, name='zs', createDate=Sat Dec 03 12:17:36 CST 2022, updateDate=Sat Dec 03 12:17:36 CST 2022}
User{id=102, name='ls', createDate=Sat Dec 03 12:17:36 CST 2022, updateDate=Sat Dec 03 12:17:36 CST 2022}
pages info : Page{count=true, pageNum=1, pageSize=2, startRow=0, endRow=2, total=4, pages=2, reasonable=false, pageSizeZero=false} 

4、插件原理

  插件本质上是拦截器,插件的相关内容在Mybatis的org.apache.ibatis.plugin包中。

 

  在plugin包中包含两个注解Intercepts、Signature;拦截器顶级接口Interceptor;拦截器链InterceptorChain;执行类Invocation、插件Plugin。

    @Intercepts、@Signature:定义要拦截哪些目标类的哪些方法

    Interceptor:

    InterceptorChain:存储全局配置中定义的插件对象信息

    Invocation:

    Pliugin:

  下面以分页插件为例,对插件原理进行分析。

1、插件配置加载

  Mybatis配置文件加载是在构建SqlSessionFactory时完成的,XMLConfigBuilder#pluginElement() 核心代码

 1 // 默认情况下,MyBatis 使用插件来拦截方法调用
 2 private void pluginElement(XNode parent) throws Exception {
 3   // 全局配置文件中对plugin标签进行了配置
 4   if (parent != null) {
 5     // 遍历全部子节点
 6     for (XNode child : parent.getChildren()) {
 7       // 获取plugins节点的interceptor属性
 8       String interceptor = child.getStringAttribute("interceptor");
 9       // 获取plugins节点下的properties配置的信息,并形成properties对象
10       Properties properties = child.getChildrenAsProperties();
11       // 实例化Interceptor对象
12       Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
13       // 设置Interceptor的属性
14       interceptorInstance.setProperties(properties);
15       // 记录interceptor对象
16       configuration.addInterceptor(interceptorInstance);
17     }
18   }
19 }

1、解析插件标签,设置属性,插件对象实例化

  获取标签中的interceptor属性中的插件的字符串全限定名,通过反射实例化插件对象;获取子标签属性信息,并通过子类实现的setProperties将数据设置到插件对象中。

  以分页插件为例,详情如下

2、插件对象添加进拦截器链

  插件对象对象添加到拦截器链中,实际上是添加到InterceptorChain的拦截器interceptors集合中。

  InterceptorChain#interceptors 详情:

private final List<Interceptor> interceptors = new ArrayList<>();

  Configuration#addInterceptor() 核心代码

1 // 拦截器链
2 protected final InterceptorChain interceptorChain = new InterceptorChain(); 
3 
4 // 插件对象(拦截器)添加到拦截器链中
5 public void addInterceptor(Interceptor interceptor) {
6   interceptorChain.addInterceptor(interceptor);
7 }

  添加拦截器链的详情如下:

2、插件拦截目标方法

  先来看看分页插件PageInterceptor中拦截的是哪个核心对象的哪些方法

1 @Intercepts(
2     {
3         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
4         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
5     }
6 )

  通过分页插件PageInterceptor注解信息,分页插件拦截的是Executor的query()方法。上述提到核心Executor对象是在创建时添加插件信息,Configuration#newExecutor() 核心代码:

 1 // 创建执行器
 2 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
 3   executorType = executorType == null ? defaultExecutorType : executorType;
 4   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
 5   Executor executor;
 6   // 根据参数,选择合适的Executor实现
 7   if (ExecutorType.BATCH == executorType) {
 8     executor = new BatchExecutor(this, transaction);
 9   } else if (ExecutorType.REUSE == executorType) {
10     executor = new ReuseExecutor(this, transaction);
11   } else {
12     executor = new SimpleExecutor(this, transaction);
13   }
14   // 根据配置决定是否开启二级缓存的功能
15   if (cacheEnabled) {
16     executor = new CachingExecutor(executor);
17   }
18   // 此处调用插件,通过插件可以改变Executor行为
19   executor = (Executor) interceptorChain.pluginAll(executor);
20   return executor;
21 }

 接下来看看分页是如何与Executor构建联系的。

 

   在执行interceptorChain.pluginAll(executor)之前,executor对象为CachingExecutor对象,调用插件处理之后,executor变成了代理对象,说明在pluginAll()方法中,对CachingExecutor做了代理。

  InterceptorChain#pluginAll() 核心代码:

1 // 遍历interceptors集合
2 public Object pluginAll(Object target) {
3   for (Interceptor interceptor : interceptors) {
4     // 调用plugin方法
5     target = interceptor.plugin(target);
6   }
7   return target;
8 }

  分页插件 PageInterceptor#plugin() 核心代码:

1 // 调用插件方法,返回代理对象
2 public Object plugin(Object target) {
3     return Plugin.wrap(target, this);
4 }

  Plugin#wrap() 核心代码:

 1 // 为目标对象Object生成代理对象
 2 public static Object wrap(Object target, Interceptor interceptor) {
 3   // 获取用户自定义Interceptor中@Signature注解的信息,getSignatureMap方法负责处理@Signture注解
 4   Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
 5   // 获取目标类型
 6   Class<?> type = target.getClass();
 7   // 获取目标类型实现的接口,拦截器可以拦截的4类对象都实现了相应的接口,这也是能使用JDK动态代理的方式创建代理对象的基础
 8   Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
 9   if (interfaces.length > 0) {
10     // 使用JDK动态代理的方式创建代理对象
11     return Proxy.newProxyInstance(
12         type.getClassLoader(),
13         interfaces,
14         // 使用InvocationHandler对象就是Plugin对象
15         new Plugin(target, interceptor, signatureMap));
16   }
17   return target;
18 }

1、获取插件注解信息

  获取分页插件PageInterceptor的@Intercepts中的注解信息,通过signatureMap集合保存注解信息。signatureMap详情如下:

2、获取目标对象的类型与接口

  目标对象的类型与接口详情如下:

3、创建代理对象并返回

  目标对象是否实现了接口,若未实现接口,返回目标对象;若实现了接口,使用JDK动态代理创建代理对象并返回。

Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        // 使用InvocationHandler对象就是Plugin对象
        new Plugin(target, interceptor, signatureMap));

  Plugin是实现了InvocationHandler接口,是JDK动态代理创建代理对象的必要参数。

public class Plugin implements InvocationHandler

  Plugin的构造函数详情如下:

1 private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
2   this.target = target;
3   this.interceptor = interceptor;
4   this.signatureMap = signatureMap;
5 }

  至此,executor的代理对象已生成,executor代理对象详情如下:

3、插件生成的代理对象完成目标方法的增强

  执行器的代理对象已经生成,接下来看看插件是如何通过代理对象处理Executor的query方法实现分页的。分页插件代理的是Executor接口中的query方法。

  DefaultSqlSession#selectList() 核心代码

 1 @Override
 2 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
 3   try {
 4     //根据statement id找到对应的MappedStatement
 5     MappedStatement ms = configuration.getMappedStatement(statement);
 6     //转而用执行器来查询结果,注意这里传入的ResultHandler是null
 7     return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
 8   } catch (Exception e) {
 9     throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
10   } finally {
11     ErrorContext.instance().reset();
12   }
13 }

  根据动态代理的特点,在执行目标query方法时,优先执行invoke()方法。在执行executor.query()方法时,会执行到Plugin的invoke()方法。

  Plugin#invoke() 核心代码:

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2   try {
 3     // 获取当前方法所在类或接口中,可被当前Interceptor拦截的方式
 4     Set<Method> methods = signatureMap.get(method.getDeclaringClass());
 5     // 如果当前调用的方式需要被拦截,则调用intercept方法进行拦截处理
 6     if (methods != null && methods.contains(method)) {
 7       //调用Interceptor.intercept
 8       return interceptor.intercept(new Invocation(target, method, args));
 9     }
10     // 如果当前调用的方法不能被拦截,则调用target对象的相应方法
11     return method.invoke(target, args);
12   } catch (Exception e) {
13     throw ExceptionUtil.unwrapThrowable(e);
14   }
15 }

1、获取分页插件拦截的目标方法详情

2、增强拦截处理

1、创建Invocation对象

  Invocation对象主要用于存储在拦截过程中需要用到的属性信息,如被代理对象、目标方法、方法参数。Invocation构造函数详情:
 1 // 调用的对象
 2 private final Object target;
 3 // 调用的方法
 4 private final Method method;
 5 // 参数
 6 private final Object[] args;
 7 
 8 public Invocation(Object target, Method method, Object[] args) {
 9   this.target = target;
10   this.method = method;
11   this.args = args;
12 }

2、执行拦截

  分页插件拦截实现,PageInterceptor#intercept() 核心伪代码:

 1 public Object intercept(Invocation invocation) throws Throwable {
 2     try {
 3         // 获取目标方法参数信息
 4         Object[] args = invocation.getArgs();
 5         // 获取目标方法参数详情
 6         MappedStatement ms = (MappedStatement) args[0];
 7         Object parameter = args[1];
 8         RowBounds rowBounds = (RowBounds) args[2];
 9         ResultHandler resultHandler = (ResultHandler) args[3];
10         // 获取目标执行器
11         Executor executor = (Executor) invocation.getTarget();
12         CacheKey cacheKey;
13         BoundSql boundSql;
14         // 4 个参数的query方法
15         if(args.length == 4){
16             boundSql = ms.getBoundSql(parameter);
17             cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
18         // 6 个参数的query方法
19         } else {
20             cacheKey = (CacheKey) args[4];
21             boundSql = (BoundSql) args[5];
22         }
23         List resultList;
24         //调用方法判断是否需要进行分页,如果不需要,直接返回查询结果
25         if (!dialect.skip(ms, parameter, rowBounds)) {
26             //判断是否需要进行 count 查询
27             if (dialect.beforeCount(ms, parameter, rowBounds)) {
28                 //创建 count 查询的缓存 key
29                 CacheKey countKey = executor.createCacheKey(ms, parameter, RowBounds.DEFAULT, boundSql);
30                 countKey.update("_Count");
31                 MappedStatement countMs = msCountMap.get(countKey);
32                 if (countMs == null) {
33                     //根据当前的 ms 创建一个返回值为 Long 类型的 ms
34                     countMs = MSUtils.newCountMappedStatement(ms);
35                     msCountMap.put(countKey, countMs);
36                 }
37                 //调用方言获取 count sql
38                 String countSql = dialect.getCountSql(ms, boundSql, parameter, rowBounds, countKey);
39                 BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
40                 //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
41                 for (String key : additionalParameters.keySet()) {
42                     countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
43                 }
44                 //执行 count 查询
45                 Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
46                 Long count = (Long) ((List) countResultList).get(0);
47                 //处理查询总数
48                 //返回 true 时继续分页查询,false 时直接返回
49                 if (!dialect.afterCount(count, parameter, rowBounds)) {
50                     //当查询总数为 0 时,直接返回空的结果
51                     return dialect.afterPage(new ArrayList(), parameter, rowBounds);
52                 }
53             }
54             //判断是否需要进行分页查询
55             if (dialect.beforePage(ms, parameter, rowBounds)) {
56                 //生成分页的缓存 key
57                 CacheKey pageKey = cacheKey;
58                 //处理参数对象
59                 parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
60                 //调用方言获取分页 sql
61                 String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
62                 BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
63                 //设置动态参数
64                 for (String key : additionalParameters.keySet()) {
65                     pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
66                 }
67                 //执行分页查询
68                 resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
69             } else {
70                 //不执行分页的情况下,也不执行内存分页
71                 resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
72             }
73         } else {
74             //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
75             resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
76         }
77         return dialect.afterPage(resultList, parameter, rowBounds);
78     } finally {
79         dialect.afterAll();
80     }
81 }
1、获取目标对象方法、方法参数及执行器
2、判断是否需要分页

  PageHelper#skip() 核心代码:

1 public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
2     Page page = pageParams.getPage(parameterObject, rowBounds);
3     if (page == null) {
4         return true;
5     } else {
6         autoDialect.initDelegateDialect(ms);
7         return false;
8     }
9 }

  会创建空的Page对象,初始化mysql的方言信息,并返回false,执行分页查询流程。

3、获取分页sql

  AbstractHelperDialect#getPageSql() 核心代码:

1 public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
2     // 获取原执行sql
3     String sql = boundSql.getSql();
4     // 获取分页
5     Page page = getLocalPage();
6     // 获取分页sql
7     return getPageSql(sql, page, pageKey);
8 }
1、获取原执行sql

  获取原执行sql,sql详情如下:

2、获取分页sql字符串

  MySqlDialect#getPageSql() 核心代码:

 1 // 获取分页sql
 2 public String getPageSql(String sql, Page page, CacheKey pageKey) {
 3     StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
 4     sqlBuilder.append(sql);
 5     if (page.getStartRow() == 0) {
 6         sqlBuilder.append(" LIMIT ");
 7         sqlBuilder.append(page.getPageSize());
 8     } else {
 9         sqlBuilder.append(" LIMIT ");
10         sqlBuilder.append(page.getStartRow());
11         sqlBuilder.append(",");
12         sqlBuilder.append(page.getPageSize());
13         pageKey.update(page.getStartRow());
14     }
15     pageKey.update(page.getPageSize());
16     return sqlBuilder.toString();
17 }

  创建 StringBuilder 对象,为原查询SQL拼接Limit分页查询,获得的分页查询语句如下

3、根据分页查询SQL字符串创建BoundSql对象

  pageBoundSql对象详情如下:

4、执行分页查询sql
  用分页查询SQL对象pageBoundSql替换原SQL对象boundSql,执行executor的query方法,获取分页数据。
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);

  替换后的boundSql对象详情如下:

  后续查询流程与普通的SQL查询一致。

5、总结

  插件相关核心流程图:

  插件的本质是拦截器,在加载配置文件时,对标签做解析,将所有的拦截器(揽件)都设置进InterceptorChain拦截器链对象中的interceptors属性中。

  通过对拦截器注解的解析,获取需要代理的对象、对象方法、方法参数等信息。在创建对应的核心对象时,通过代理模式创建代理对象,通过代理对象执行目标对象的目标方法时做增强处理。

   

posted @ 2023-03-25 20:22  无虑的小猪  阅读(147)  评论(0编辑  收藏  举报