PageHelper插件相关注解说明
PageHelperAutoConfiguration
类的相关注解说明:@Configuration
:相当于<beans></beans>
标签@ConditionalOnBean(SqlSessionFactory.class)
:只有在上下文中存在SqlSessionFactory
的bean
是才会运行PageHelperAutoConfiguration
@EnableConfigurationProperties(PageHelperProperties.class)
:@ConfigurationProperties
注解主要用来把properties
配置文件转化为bean
来使用的,而@EnableConfigurationProperties
注解的作用是@ConfigurationProperties
注解生效。如果只配置@ConfigurationProperties
注解,在IOC
容器中是获取不到properties
配置文件转化的bean
的。@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
:PageHelperProperties
是PageHelper
的配置类,将properties
中的配置文件转换成PageHelperProperties
对象,PageHelperProperties
上有@ConfigurationProperties(prefix = "PageHelperProperties.PAGEHELPER_PREFIX")
注解。@AutoConfigureAfter(MybatisAutoConfiguration.class)
:当MybatisAutoConfiguration
这个类加载完成后再加载本类@PostConstruct
该注解被用来修饰一个非静态的void()
方法。被@PostConstruct
修饰的方法会在服务器加载Servlet
的时候运行,并且只会被服务器执行一次。执行顺序:Constructor
(构造方法) ->@Autowired
(依赖注入) ->@PostConstruct
(注释的方法)
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
:表示拦截的方法,这里拦截的是Executor
的query
方法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
是由SqlSessionFactory
的oppenSession()
方法来生成的,生成会话的过程会通过Configuration
类的newExecutor
方法创建一个执行器。
Mybatis
执行器Executor
根据SqlSession
传递的参数执行query
方法,经过一系列的转折,最后会创建一个StatementHandler
对象,然后将必要的参数传递给StatementHandler
,使用StatementHandler
来完成对数据库的查询,最终返回List
结果集
StatementHandler
主要完成以下两个工作:
- 对于
JDBC
的PreparedStatement
类型的对象,创建过程中,我们使用的是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; }
从Mybatis
的SQL
执行流程图中可以Mybatis
的四大对象Executor
、ParameterHandler
、ResultSetHandler
、StatementHandler
,由他们一起合作完成SQL
的执行,那么这四大对象是由谁创建的呢?没错,就是Mybatis
的配置中心:Configuration
,创建源代码如上:
这些方法最终都会调用interceptorChain.pluginAll
方法,这个方法引用的Plugin
的wrap
方法
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