PageHelper源码详解

3次阅读

共计 11257 个字符,预计需要花费 29 分钟才能阅读完成。

PageHelper 插件相关注解说明

  1. PageHelperAutoConfiguration类的相关注解说明:

    • @Configuration:相当于 <beans></beans> 标签
    • @ConditionalOnBean(SqlSessionFactory.class):只有在上下文中存在 SqlSessionFactorybean是才会运行PageHelperAutoConfiguration
    • @EnableConfigurationProperties(PageHelperProperties.class)@ConfigurationProperties注解主要用来把 properties 配置文件转化为 bean 来使用的,而 @EnableConfigurationProperties 注解的作用是 @ConfigurationProperties 注解生效。如果只配置 @ConfigurationProperties 注解,在 IOC 容器中是获取不到 properties 配置文件转化的 bean 的。
    • @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)PageHelperPropertiesPageHelper 的配置类,将 properties 中的配置文件转换成 PageHelperProperties 对象,PageHelperProperties上有 @ConfigurationProperties(prefix = "PageHelperProperties.PAGEHELPER_PREFIX") 注解。
    • @AutoConfigureAfter(MybatisAutoConfiguration.class):当 MybatisAutoConfiguration 这个类加载完成后再加载本类
    • @PostConstruct该注解被用来修饰一个非静态的 void() 方法。被 @PostConstruct 修饰的方法会在服务器加载 Servlet 的时候运行,并且只会被服务器执行一次。执行顺序:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
  2. PageInterceptor类的相关注解说明

    @Intercepts({@Signature(  
         type \= Executor.class,  
         method \= "query",  
         args \= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}  
        ), @Signature(  
         type \= Executor.class,  
         method \= "query",  
         args \= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}  
        )})
  • type:表示拦截的类,这里是 Excutor 的实现类
  • method:表示拦截的方法,这里拦截的是 Executorquery方法
  • args:表示方法参数

Mybatis 拦截器原理分析

Mybatis提供的插件(Plugin)功能其实就是拦截器功能,默认情况下,Mybatis允许使用插件来拦截方法的调用包括一下几种:

 1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) // 拦截执行器的方法  
 2. ParameterHandler (getParameterObject, setParameters) // 拦截参数的方法  
 3. ResultSetHandler (handleResultSets, handleOutputParameters) // 拦截结果集的方法  
 4. StatementHandler (prepare, parameterize, batch, update, query) // 拦截 Sql 语法构建的处理

PageHelper 自动配置

pageHelper的自动配置类名:PageHelperAutoConfiguration

 @Configuration  
 @ConditionalOnBean(SqlSessionFactory.class)  
 @EnableConfigurationProperties(PageHelperProperties.class)  
 @AutoConfigureAfter(MybatisAutoConfiguration.class)  
 public class PageHelperAutoConfiguration {  
    
 @Autowired  
 private List<SqlSessionFactory\> sqlSessionFactoryList;  
    
 @Autowired  
 private PageHelperProperties properties;  
    
 /\*\*  
 \* 接受分页插件额外的属性  
 \*  
 \* @return  
 \*/  
 @Bean  
 @ConfigurationProperties(prefix \= PageHelperProperties.PAGEHELPER\_PREFIX)  
 public Properties pageHelperProperties() {return new Properties();  
 }  
    
 @PostConstruct  
 public void addPageInterceptor() {PageInterceptor interceptor \= new PageInterceptor();  
 Properties properties \= new Properties();  
 // 先把一般方式配置的属性放进去  
 properties.putAll(pageHelperProperties());  
 // 在把特殊配置放进去,由于 close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步  
 properties.putAll(this.properties.getProperties());  
 interceptor.setProperties(properties);  
 for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {sqlSessionFactory.getConfiguration().addInterceptor(interceptor);  
 }  
 }  
    
 }  

在执行完自动配置的构造方法后,会执行 addPageInterceptor 方法,配置属性的注入,并将 PageInterceptor 实例添加到 Configuration 的实例的 interceptorChain

Mybatis 构件相互关系

Mybatis的核心构件:

  • SqlSession 作为 MyBatis 工作的主要顶层 API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护
  • StatementHandler 封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 Statement 结果集转换成 List 集合。
  • ParameterHandler 负责对用户传递的参数转换成 JDBC Statement 所需要的参数,
  • ResultSetHandler 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合;
  • TypeHandler 负责 java 数据类型和 jdbc 数据类型之间的映射和转换
  • MappedStatement MappedStatement 维护了一条 <select|update|delete|insert> 节点的封装,
  • SqlSource 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回
  • BoundSql 表示动态生成的 SQL 语句以及相应的参数信息
  • Configuration MyBatis 所有的配置信息都维持在 Configuration 对象之中。

Mybatis 执行流程说明

 public static void main(String\[\] args) throws Exception {  
 /\*  
 \* 1. 加载 mybatis 的配置文件,初始化 mybatis,创建出 SqlSessionFactory,是创建 SqlSession 的工厂  
 \* 这里只是为了演示的需要,SqlSessionFactory 临时创建出来,在实际的使用中,SqlSessionFactory 只需要创建一次,当作单例来使用  
 \*/  
 InputStream inputStream \= Resources.getResourceAsStream("mybatisConfig.xml");  
 SqlSessionFactoryBuilder builder \= new SqlSessionFactoryBuilder();  
 SqlSessionFactory factory \= builder.build(inputStream);  
    
 //2. 从 SqlSession 工厂 SqlSessionFactory 中创建一个 SqlSession,进行数据库操作  
 SqlSession sqlSession \= factory.openSession();}

 public SqlSession openSession() {return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);  
 }  
 private SqlSession openSessionFromDataSource(ExecutorType execType,  TransactionIsolationLevel level, boolean autoCommit) {  
 Transaction tx \= null;  
    
 DefaultSqlSession var8;  
 try {Environment environment \= this.configuration.getEnvironment();  
 TransactionFactory transactionFactory \= this.getTransactionFactoryFromEnvironment(environment);  
 tx \= transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);  
 Executor executor \= this.configuration.newExecutor(tx, execType);  
 var8 \= new DefaultSqlSession(this.configuration, executor, autoCommit);  
 } catch (Exception var12) {this.closeTransaction(tx);  
 throw ExceptionFactory.wrapException("Error opening session.  Cause:" + var12, var12);  
 } finally {ErrorContext.instance().reset();}  
    
 return var8;  
 }

通过代码可以看出 SqlSession 是由 SqlSessionFactoryoppenSession()方法来生成的,生成会话的过程会通过 Configuration 类的 newExecutor 方法创建一个执行器。

Mybatis执行器 Executor 根据 SqlSession 传递的参数执行 query 方法,经过一系列的转折,最后会创建一个 StatementHandler 对象,然后将必要的参数传递给 StatementHandler,使用StatementHandler 来完成对数据库的查询,最终返回 List 结果集

StatementHandler主要完成以下两个工作:

  • 对于 JDBCPreparedStatement类型的对象,创建过程中,我们使用的是 SQL 语句字符串会包含若干个? 占位符,我们其后再对占位符进行设值。
  • StatementHandler通过 List<E> query(Statement statement, ResultHandler resultHandler) 方法来完成执行 Statement,和将Statement 对象返回的 resultSet 封装成List
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler \= mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);  
 parameterHandler \= (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  
 return parameterHandler;  
 }  
    
 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,  
 ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler \= new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);  
 resultSetHandler \= (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);  
 return resultSetHandler;  
 }  
    
 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler \= new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);  
 statementHandler \= (StatementHandler) interceptorChain.pluginAll(statementHandler);  
 return statementHandler;  
 }  
    
 public Executor newExecutor(Transaction transaction) {return newExecutor(transaction, defaultExecutorType);  
 }  
    
 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {  
 executorType \= executorType \== null ? defaultExecutorType : executorType;  
 executorType \= executorType \== null ? ExecutorType.SIMPLE : executorType;  
 Executor executor;  
 if (ExecutorType.BATCH \== executorType) {executor \= new BatchExecutor(this, transaction);  
 } else if (ExecutorType.REUSE \== executorType) {executor \= new ReuseExecutor(this, transaction);  
 } else {executor \= new SimpleExecutor(this, transaction);  
 }  
 if (cacheEnabled) {executor \= new CachingExecutor(executor);  
 }  
 executor \= (Executor) interceptorChain.pluginAll(executor);  
 return executor;  
 }

​ 从 MybatisSQL执行流程图中可以 Mybatis 的四大对象 ExecutorParameterHandlerResultSetHandlerStatementHandler,由他们一起合作完成SQL 的执行,那么这四大对象是由谁创建的呢?没错,就是 Mybatis 的配置中心:Configuration,创建源代码如上:

这些方法最终都会调用 interceptorChain.pluginAll 方法,这个方法引用的 Pluginwrap方法

 public static Object wrap(Object target, Interceptor interceptor) {  
 // 获取 PageInterceptor 的 Intercepts 注解中 @Signature 的 method,存放到 Plugin 的 signatureMap 中  
 Map<Class<?>, Set<Method\>> signatureMap \= getSignatureMap(interceptor);  
 Class<?> type \= target.getClass();  
 // 获取目标对象实现的全部接口;四大对象是接口,都有默认的子类实现  
 // JDK 的动态代理只支持接口  
 Class<?>\[\] interfaces \= getAllInterfaces(type, signatureMap);  
 if (interfaces.length \> 0) {  
 return Proxy.newProxyInstance(type.getClassLoader(),  
 interfaces,  
 new Plugin(target, interceptor, signatureMap));  
 }  
 return target;  
 }

pluginAll其实就是给四大对象创建代理,一个 Interceptor 就会创建一层代理,而我们的 PageInterceptor 只是其中一层代理;我们接着往下看,Plugin继承了 InvocationHandler,JDK 的动态代理,那么它的 invoke 方法肯定会被调用

 @Override  
 public Object invoke(Object proxy, Method method, Object\[\] args) throws Throwable {  
 try {  
 // 获取 PageInterceptor 的 Intercepts 注解中 @Signature 的 method  
 Set<Method\> methods \= signatureMap.get(method.getDeclaringClass());  
 // 当 methods 包含目标方法时,调用 PageInterceptor 的 intercept 方法完成 SQL 的分页处理  
 if (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));  
 }  
 return method.invoke(target, args);  
 } catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);  
 }  
 }

从代码中可以看出,最后调用了 PageInterceptor 拦截器的 intercept 方法

 @Override  
 public Object intercept(Invocation invocation) throws Throwable {  
 try {Object\[\] args \= invocation.getArgs();  
 MappedStatement ms \= (MappedStatement) args\[0\];  
 Object parameter \= args\[1\];  
 RowBounds rowBounds \= (RowBounds) args\[2\];  
 ResultHandler resultHandler \= (ResultHandler) args\[3\];  
 Executor executor \= (Executor) invocation.getTarget();  
 CacheKey cacheKey;  
 BoundSql boundSql;  
 // 由于逻辑关系,只会进入一次  
 if(args.length \== 4){  
 //4 个参数时  
 boundSql \= ms.getBoundSql(parameter);  
 cacheKey \= executor.createCacheKey(ms, parameter, rowBounds, boundSql);  
 } else {  
 //6 个参数时  
 cacheKey \= (CacheKey) args\[4\];  
 boundSql \= (BoundSql) args\[5\];  
 }  
 List resultList;  
 // 调用方法判断是否需要进行分页,如果不需要,直接返回结果  
 // 此处会从当前线程取 Page 信息,还记得什么时候在哪将 Page 信息放进当前线程的吗?if (!dialect.skip(ms, parameter, rowBounds)) {  
 // 反射获取动态参数  
 String msId \= ms.getId();  
 Configuration configuration \= ms.getConfiguration();  
 Map<String, Object\> additionalParameters \= (Map<String, Object\>) additionalParametersField.get(boundSql);  
 // 判断是否需要进行 count 查询  
 if (dialect.beforeCount(ms, parameter, rowBounds)) {  
 String countMsId \= msId + countSuffix;  
 Long count;  
 // 先判断是否存在手写的 count 查询  
 MappedStatement countMs \= getExistedMappedStatement(configuration, countMsId);  
 if(countMs != null){count \= executeManualCount(executor, countMs, parameter, boundSql, resultHandler);  
 } else {countMs \= msCountMap.get(countMsId);  
 // 自动创建  
 if (countMs \== null) {  
 // 根据当前的 ms 创建一个返回值为 Long 类型的 ms  
 countMs \= MSUtils.newCountMappedStatement(ms, countMsId);  
 msCountMap.put(countMsId, countMs);  
 }  
 count \= executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);  
 }  
 // 处理查询总数  
 // 返回 true 时继续分页查询,false 时直接返回  
 if (!dialect.afterCount(count, parameter, rowBounds)) {  
 // 当查询总数为 0 时,直接返回空的结果  
 return dialect.afterPage(new ArrayList(), parameter, rowBounds);  
 }  
 }  
 // 判断是否需要进行分页查询  
 if (dialect.beforePage(ms, parameter, rowBounds)) {  
 // 生成分页的缓存 key  
 CacheKey pageKey \= cacheKey;  
 // 处理参数对象  
 parameter \= dialect.processParameterObject(ms, parameter, boundSql, pageKey);  
 // 调用方言获取分页 sql  
 String pageSql \= dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);  
 BoundSql pageBoundSql \= new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);  
 // 设置动态参数  
 for (String key : additionalParameters.keySet()) {pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));  
 }  
 // 执行分页查询  
 resultList \= executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);  
 } else {  
 // 不执行分页的情况下,也不执行内存分页  
 resultList \= executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);  
 }  
 } else {  
 //rowBounds 用参数值,不使用分页插件处理时,仍然支持默认的内存分页  
 resultList \= executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);  
 }  
 return dialect.afterPage(resultList, parameter, rowBounds);  
 } finally {dialect.afterAll();  
 }  
 }

其中会读取当前线程中的 Page 信息,根据 Page 信息来断定是否需要分页;而 Page 信息就是从我们的业务代码中存放到当前线程的.

参考资料 & 鸣谢


  • https://www.cnblogs.com/youzhibing/p/9603149.html
  • https://blog.csdn.net/luanlouis/article/details/40422941
  • https://juejin.im/post/5d085229f265da1bd605a629
正文完
 0