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对象
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
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
替换后的boundSql对象详情如下:
后续查询流程与普通的SQL查询一致。
5、总结
插件相关核心流程图:
插件的本质是拦截器,在加载配置文件时,对标签做解析,将所有的拦截器(揽件)都设置进InterceptorChain拦截器链对象中的interceptors属性中。