Detailed explanation of PageHelper source code

Time:2021-1-16

Notes on PageHelper plug-in

  1. PageHelperAutoConfigurationClass notes:

    • @Configuration: equivalent to<beans></beans>label
    • @ConditionalOnBean(SqlSessionFactory.class): exists only in contextSqlSessionFactoryOfbeanThat’s how it worksPageHelperAutoConfiguration
    • @EnableConfigurationProperties(PageHelperProperties.class)@ConfigurationPropertiesAnnotations are mainly used topropertiesProfile tobeanTo use, and@EnableConfigurationPropertiesWhat is the function of annotation@ConfigurationPropertiesThe annotation takes effect. If you only configure@ConfigurationPropertiesNotes, inIOCCan’t get in containerpropertiesTransformation of configuration filebeanYes.
    • @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)PageHelperPropertiesyesPageHelperThe configuration class of thepropertiesThe configuration file in is converted toPageHelperPropertiesObject,PageHelperPropertiesIt’s on the table@ConfigurationProperties(prefix = "PageHelperProperties.PAGEHELPER_PREFIX")Notes.
    • @AutoConfigureAfter(MybatisAutoConfiguration.class): whenMybatisAutoConfigurationLoad this class after loading this class
    • @PostConstructThis annotation is used to modify a non staticvoid()method. cover@PostConstructThe modified method will be loaded on the serverServletAnd will only be executed once by the server. Execution sequence:Constructor(construction method) – >@Autowired(dependency injection) – >@PostConstruct(method of annotation)
  2. PageInterceptorClass notes

    @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: represents the intercepted class, here isExcutorImplementation class of
  • method: means the method of interception. Here is the method of interceptionExecutorOfquerymethod
  • args: represents method parameters

Principle analysis of mybatis interceptor

MybatisPlug in provided(Plugin)Function is actually interceptor function. By default,MybatisPlug ins are allowed to intercept method calls, including the following:

1. Executor (update, query, flushstatements, commit, rollback, gettransaction, close, isclosed) // method of intercepting executor  
 2. Parameterhandler (getparameterobject, setparameters) // method of intercepting parameters  
 3. Resultsethandler (handleresultsets, handleoutputparameters) // method of intercepting result set  
 4. Statementhandler (prepare, parameterize, batch, update, query) // intercepts the processing of SQL syntax construction

PageHelper auto configuration

pageHelperAuto configuration class name for:PageHelperAutoConfiguration

@Configuration  
 @ConditionalOnBean(SqlSessionFactory.class)  
 @EnableConfigurationProperties(PageHelperProperties.class)  
 @AutoConfigureAfter(MybatisAutoConfiguration.class)  
 public class PageHelperAutoConfiguration {  
    
 @Autowired  
 private List<SqlSessionFactory\> sqlSessionFactoryList;  
    
 @Autowired  
 private PageHelperProperties properties;  
    
 /\*\*  
 \*Accept additional properties of the paging plug-in  
 \*  
 \* @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();  
 //First, put in the properties configured in the general way  
 properties.putAll(pageHelperProperties());  
 //When the special configuration is put in, because close conn uses the above method, the attribute name is close conn instead of closeconn, so an extra step is needed  
 properties.putAll(this.properties.getProperties());  
 interceptor.setProperties(properties);  
 for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {  
 sqlSessionFactory.getConfiguration().addInterceptor(interceptor);  
 }  
 }  
    
 }

After the auto configuration construction method is executed, theaddPageInterceptorMethod, configure the property injection, andPageInterceptorInstance added toConfigurationExamples ofinterceptorChainin

Mybatis component relationships

MybatisThe core components of the system are as follows:

  • SqlSessionAs the main top-level API of mybatis, it represents the interaction session with the database and completes the necessary database addition, deletion, modification and query functions
  • ExecutorMybatis executor is the core of mybatis scheduling, responsible for SQL statement generation and query cache maintenance
  • StatementHandlerIt encapsulates the JDBC statement operation and is responsible for the operation of JDBC statement, such as setting parameters and converting statement result set into list set.
  • ParameterHandlerIt is responsible for converting the parameters passed by users into the parameters required by JDBC statement,
  • ResultSetHandlerIt is responsible for converting the resultset object returned by JDBC into a list type collection;
  • TypeHandlerResponsible for the mapping and conversion between Java data type and JDBC data type
  • MappedStatementMappedstatement maintains an encapsulation of < select | update | delete | Insert > nodes,
  • SqlSourceIt is responsible for dynamically generating SQL statements according to the parameterobject passed by the user, encapsulating the information into the boundsql object, and returning the
  • BoundSqlRepresents the dynamically generated SQL statement and the corresponding parameter information
  • ConfigurationAll configuration information of mybatis is maintained in the configuration object.

Detailed explanation of PageHelper source code

Mybatis execution process description

public static void main(String\[\] args) throws Exception {  
 /\*  
 \*1. Load mybatis configuration file, initialize mybatis, and create sqlsession factory, which is the factory to create sqlsession  
 \*This is just for the sake of demonstration. Sqlsessionfactory is created temporarily. In actual use, sqlsessionfactory only needs to be created once and used as a singleton  
 \*/  
 InputStream inputStream \= Resources.getResourceAsStream("mybatisConfig.xml");  
 SqlSessionFactoryBuilder builder \= new SqlSessionFactoryBuilder();  
 SqlSessionFactory factory \= builder.build(inputStream);  
    
 //2. Create a sqlsession from the sqlsession factory to operate the database  
 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;  
 }

You can see from the codeSqlSessionBySqlSessionFactoryOfoppenSession()Method, and the process of generating the session willConfigurationClassnewExecutorMethod to create an actuator.

MybatisActuatorExecutoraccording toSqlSessionParameter execution passedqueryMethod. After a series of twists and turns, aStatementHandlerObject, and then pass the necessary parameters to theStatementHandler, usingStatementHandlerTo complete the query of the database, and finally return to theListResult set

StatementHandlerThe main tasks are as follows

  • aboutJDBCOfPreparedStatementIn the process of creation, we useSQLThe statement string will contain several? Placeholders, and we will set the value of the placeholders later.
  • StatementHandleradoptList<E> query(Statement statement, ResultHandler resultHandler)Method to complete the executionStatement, and willStatementObjectresultSetPackage intoList
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;  
 }

FromMybatisOfSQLIn the execution flow chart, you canMybatisThe four major objects ofExecutorParameterHandlerResultSetHandlerStatementHandlerAnd they work togetherSQLWho created these four objects? Yes, it isMybatisConfiguration center for:Configuration, create the source code as above:

These methods will eventually be calledinterceptorChain.pluginAllMethod, which refers to thePluginOfwrapmethod

public static Object wrap(Object target, Interceptor interceptor) {  
 //Get the method of @ signature in the intercepts annotation of pageinterceptor and store it in the signaturemap of plugin  
 Map<Class<?>, Set<Method\>> signatureMap \= getSignatureMap(interceptor);  
 Class<?> type \= target.getClass();  
 //Get all the interfaces implemented by the target object; the four objects are interfaces with default subclass implementation  
 //JDK's dynamic proxy only supports interfaces  
 Class<?>\[\] interfaces \= getAllInterfaces(type, signatureMap);  
 if (interfaces.length \> 0) {  
 return Proxy.newProxyInstance(  
 type.getClassLoader(),  
 interfaces,  
 new Plugin(target, interceptor, signatureMap));  
 }  
 return target;  
 }

pluginAllIn fact, it is to create agents for four major objects, oneInterceptorA layer of agents will be created, and ourPageInterceptorIt’s just one of the agents; let’s move on,PluginInheritedInvocationHandler,JDKDynamic proxy, then itsinvokeMethod will definitely be called

@Override  
 public Object invoke(Object proxy, Method method, Object\[\] args) throws Throwable {  
 try {  
 //Get the method of @ signature in the intercepts annotation of pageinterceptor  
 Set<Method\> methods \= signatureMap.get(method.getDeclaringClass());  
 //When methods contains the target method, call the intercept method of pageinterceptor to complete the paging of 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);  
 }  
 }

You can see from the code that the last call has been made.PageInterceptorDesign of interceptorinterceptmethod

@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;  
 //Because of the logical relationship, it will only enter once  
 if(args.length \== 4){  
 //4 parameters  
 boundSql \= ms.getBoundSql(parameter);  
 cacheKey \= executor.createCacheKey(ms, parameter, rowBounds, boundSql);  
 } else {  
 //6 parameters  
 cacheKey \= (CacheKey) args\[4\];  
 boundSql \= (BoundSql) args\[5\];  
 }  
 List resultList;  
 //Call the method to determine whether pagination is needed. If not, directly return the result  
 //The page information will be taken from the current thread here. Do you remember when and where to put the page information into the current thread?  
 if (!dialect.skip(ms, parameter, rowBounds)) {  
 //Get dynamic parameters by reflection  
 String msId \= ms.getId();  
 Configuration configuration \= ms.getConfiguration();  
 Map<String, Object\> additionalParameters \= (Map<String, Object\>) additionalParametersField.get(boundSql);  
 //Judge whether count query is needed  
 if (dialect.beforeCount(ms, parameter, rowBounds)) {  
 String countMsId \= msId + countSuffix;  
 Long count;  
 //First judge whether there is a handwritten count query  
 MappedStatement countMs \= getExistedMappedStatement(configuration, countMsId);  
 if(countMs != null){  
 count \= executeManualCount(executor, countMs, parameter, boundSql, resultHandler);  
 } else {  
 countMs \= msCountMap.get(countMsId);  
 //Automatic creation  
 if (countMs \== null) {  
 //Create a MS with a return value of long type according to the current Ms  
 countMs \= MSUtils.newCountMappedStatement(ms, countMsId);  
 msCountMap.put(countMsId, countMs);  
 }  
 count \= executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);  
 }  
 //Total number of queries processed  
 //When it returns true, it will continue paging query, and when it returns false, it will return directly  
 if (!dialect.afterCount(count, parameter, rowBounds)) {  
 //When the total number of queries is 0, empty results are returned directly  
 return dialect.afterPage(new ArrayList(), parameter, rowBounds);  
 }  
 }  
 //Determine whether pagination query is needed  
 if (dialect.beforePage(ms, parameter, rowBounds)) {  
 //Generate paged cache key  
 CacheKey pageKey \= cacheKey;  
 //Processing parameter objects  
 parameter \= dialect.processParameterObject(ms, parameter, boundSql, pageKey);  
 //Call dialect to get paging SQL  
 String pageSql \= dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);  
 BoundSql pageBoundSql \= new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);  
 //Setting dynamic parameters  
 for (String key : additionalParameters.keySet()) {  
 pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));  
 }  
 //Perform paging query  
 resultList \= executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);  
 } else {  
 //If paging is not performed, memory paging is not performed  
 resultList \= executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);  
 }  
 } else {  
 //Rowbounds still supports the default memory paging when the parameter value is used and the paging plug-in is not used  
 resultList \= executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);  
 }  
 return dialect.afterPage(resultList, parameter, rowBounds);  
 } finally {  
 dialect.afterAll();  
 }  
 }

It reads thePageInformation, according toPageInformation to determine whether pagination is needed; andPageInformation is stored in the current thread from our business code

References & thanks


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

Recommended Today

Choose react or angular 2

Original addressChoosing between React vs. Angular 2The following is the translation of this article, which can be used at your choiceReactperhapsAngular2We need to help when we need to. React has become a cool representative in 2015, but angular.js has changed from a front-end framework that people love to a terrible devil (and not so terrible…) […]