首页 > 编程语言 >Mybatis源码(十):Mybatis插件机制

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

时间:2023-03-25 20:35:17浏览次数:40  
标签:执行器 插件 对象 Object 源码 executor 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属性中。

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

   

标签:执行器,插件,对象,Object,源码,executor,Mybatis,方法
From: https://www.cnblogs.com/RunningSnails/p/17255482.html

相关文章

  • 掌握这5款 Edge插件,让你的浏览器使用更高效!
    您好,今天我想向您推荐两款不常用的Edge插件,它们可以帮助您更高效地浏览网页和管理标签页。这些插件可能不像一些常见的插件那样广为人知,但它们的功能却非常实用。IHome......
  • SQL SERVER 安装SQL Prompt10插件完整教程
    SQLPrompt的作用这里不做详细描述,不清楚的可以自行百度,这里只对该插件的安装、激活过程做详细说明。下面我的SSMS版本信息,版本较高者建议安装SQLPrompt10版本。 1.......
  • modbus CRC校验源码转载
     c#CRC校验 用于学习记录原文载自:https://www.cnblogs.com/ayxj/p/11481969.html用C#实现的几种常用数据校验方法整理(CRC校验;LRC校验;BCC校验;累加和校验)   ......
  • Qt源码阅读(一) 信号槽的连接与调用
    信号槽连接目录信号槽连接1.信号的连接2槽的调用信号槽的连接,其实内部本质还是一个回调函数,主要是维护了信号发送Object的元对象里一个连接的列表。调用connect函数时,......
  • 三大框架——持久层框架Mybatis
    持久层框架MyBatis1.mybatis介绍2.执行流程3.使用步骤代码实现举例:商品分类CRUD操作4.关联关系4.1一对一4.2一对多5.参数占位符6.复杂搜索6.......
  • MyBatis机制介绍与原理
    插件简介什么是插件插件是一种软件组件,可以在另一个软件程序中添加功能或特性。插件通常被设计成可以随时添加或删除的,而不影响主程序的功能。插件可以扩展软件程序的功......
  • mybatis实现查看详情
    查看详情1.编写接口方法:Mapper接口***参数:id结果BrandBrandselectByld(intid);2.编写SQL语句:SQL映射文件......
  • 若依框架----源码分析(@RateLimiter)
    若依作为最近非常火的脚手架,分析它的源码,不仅可以更好的使用它,在出错时及时定位,也可以在需要个性化功能时轻车熟路的修改它以满足我们自己的需求,同时也可以学习人家解决问题......
  • 若依框架----源码分析(@Log)
    若依作为最近非常火的脚手架,分析它的源码,不仅可以更好的使用它,在出错时及时定位,也可以在需要个性化功能时轻车熟路的修改它以满足我们自己的需求,同时也可以学习人家解决问题......
  • DETR源码学习(一)之网络模型构建
    这篇文章主要为记录DETR模型的构建过程首先明确DETR模型的搭建顺序:首先是backbone的搭建,使用的是resnet50,随后是Transformer模型的构建,包含编码器的构建与解码器的构建,完......