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