Data paging function is a necessary function in our software system, in the case of persistent layer using Mybatis, pageHelper to achieve background paging is a common choice, so this article special class introduction.
PageHelper principle
Related,
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.28.</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>1.215.</version>
</dependency>
Copy the code
1. Add the plugin
To use PageHelper, first configure it in mybatis global configuration file. As follows:
<? xml version="1.0" encoding="UTF-8"? > <! DOCTYPE configuration PUBLIC"- / / mybatis.org//DTD Config / 3.0 / EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <plugins> <! -- com.github. Pagehelper specifies the package name of the pageHelper class --> <plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/ > <! -- The default value isfalse-- > <! - set totrueThe first RowBounds argument, offset, is used as the pageNum page number --> <! -- same as pageNum in startPage --> <property name="offsetAsPageNum" value="true"/ > <! -- The default value isfalse-- > <! - set totrueThe count query is performed using RowBounds paging --> <property name="rowBoundsWithCount" value="true"/ > <! - set totrueIf pageSize=0Or RowBounds. Limit =0All results will be queried --> <! --> <property name="pageSizeZero" value="true"/ > <! --3.3. 0Version available - Paging parameter rationalization, defaultfalseDisabled - > <! -- Enable rationalization if pageNum<1Select * from pageNum>pages; select * from pageNum>pages; -- Disable rationalization if pageNum<1Or pageNum>pages returns null data --> <property name="reasonable" value="false"/ > <! --3.5. 0Version available - in order to support the startPage(Object Params) method -> <! -- Added a 'params' parameter to configure parameter mapping for values from Map or ServletRequest --> <! - you can configure the pageNum, pageSize, count, pageSizeZero, reasonable, not configure mapping with default values -- -- > <! Do not copy this configuration without understanding the meaning --> <property name="params" value="pageNum=start; pageSize=limit;"/ > <! -- always always returns PageInfo, check checks whether the return type is PageInfo, None returns Page --> < Property name="returnPageInfo" value="check" />
</plugin>
</plugins>
</configuration>
Copy the code
2. Loading process
Let’s demonstrate this with the following lines of code
// Get the configuration file
InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
// Get the SqlSessionFactory object by loading the configuration file
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// Get the SqlSession object
SqlSession session = factory.openSession();
PageHelper.startPage(1.5);
session.selectList("com.bobo.UserMapper.query");
Copy the code
Loading the configuration file let’s start with this line of code
new SqlSessionFactoryBuilder().build(inputStream);
Copy the code
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null.null);
}
Copy the code
private void pluginElement(XNode parent) throws Exception {
if(parent ! =null) {
for (XNode child : parent.getChildren()) {
/ / access to content: com. Making. Pagehelper. Pagehelper
String interceptor = child.getStringAttribute("interceptor");
// Get the configured attribute information
Properties properties = child.getChildrenAsProperties();
// Create an interceptor instance
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// Bind attributes to interceptors
interceptorInstance.setProperties(properties);
// This method needs to be checkedconfiguration.addInterceptor(interceptorInstance); }}}Copy the code
public void addInterceptor(Interceptor interceptor) {
// Add interceptors to the interceptor chain, which is essentially an ordered collection of lists
interceptorChain.addInterceptor(interceptor);
}
Copy the code
Summary: By fetching the SqlSessionFactory object, we load the global configuration file and mapping file and add the configured interceptor to the interceptor chain.
3. Interception information defined by PageHelper
Let’s take a look at the header definition for PageHelper
@SuppressWarnings("rawtypes")
@Intercepts( @Signature( type = Executor.class, method = "query", args = {MappedStatement.class , Object.class , RowBounds.class , ResultHandler.class }))
public class PageHelper implements Interceptor {
/ / SQL tools
private SqlUtil sqlUtil;
// Attribute parameter information
private Properties properties;
// Set the object mode
private SqlUtilConfig sqlUtilConfig;
Dialect automatically obtains dialect, which can work without setProperties or setSqlUtilConfig
private boolean autoDialect = true;
// The dialect is automatically obtained at runtime
private boolean autoRuntimeDialect;
// In the case of multiple data sources, determine whether to close the data source after obtaining jdbcurl
private boolean closeConn = true;
Copy the code
// The interceptor object is defined
// query(MappedStatement ms,Object o,RowBounds ob ResultHandler rh)
// This method
type = Executor.class,
method = "query",
args = {MappedStatement.class
, Object.class
, RowBounds.class
, ResultHandler.class
}))
Copy the code
PageHelper defines what methods the interceptor intercepts.
4.Executor
Next we need to analyze what happens to the Executor during the SqlSession instantiation. We need to start tracing from this line of code
SqlSession session = factory.openSession();
Copy the code
public SqlSession openSession(a) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}
Copy the code
Strengthen the Executor
At this point we see that the Executor object is actually enhanced by the proxy class we live in. The invoke code is
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// Execute the intercept method if it is defined
if(methods ! =null && methods.contains(method)) {
// Enter to view the method enhancement
return interceptor.intercept(new Invocation(target, method, args));
}
// Not the method that needs to intercept is executed directly
return method.invoke(target, args);
} catch (Exception e) {
throwExceptionUtil.unwrapThrowable(e); }}Copy the code
/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
if (autoDialect) {
initSqlUtil(invocation);
}
returnsqlUtil.processPage(invocation); }}Copy the code
We will examine this method later. Now that we’re done with Executor’s analysis, let’s look at how PageHelper implements paging.
5. Paging process
Next, we’ll look at the paging process through code trace. We’ll start with two lines of code:
5.1 startPage
PageHelper.startPage(1.5);
Copy the code
/** * start paging **@param params
*/
public static <E> Page<E> startPage(Object params) {
Page<E> page = SqlUtil.getPageFromObject(params);
// When Order Derby has already been executed
Page<E> oldPage = SqlUtil.getLocalPage();
if(oldPage ! =null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
SqlUtil.setLocalPage(page);
return page;
}
Copy the code
/** * start paging **@paramPageNum page *@paramPageSize Number of displays per page *@paramCount Whether to perform count query *@paramReasonable pagination is reasonable. When null, the default configuration */ is used
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable) {
return startPage(pageNum, pageSize, count, reasonable, null);
}
Copy the code
/** * start paging **@paramOffset page *@paramLimit Number of pages displayed *@paramCount Whether to perform count query */
public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {
Page<E> page = new Page<E>(new int[]{offset, limit}, count);
// When Order Derby has already been executed
Page<E> oldPage = SqlUtil.getLocalPage();
if(oldPage ! =null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
// This is important!!
SqlUtil.setLocalPage(page);
return page;
}
Copy the code
private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
// Save paging information in ThreadLocal, thread-safe!
public static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
Copy the code
5.2 selectList method
session.selectList("com.bobo.UserMapper.query");
Copy the code
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
Copy the code
We need to go back to the Invoke method and continue
/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
if (autoDialect) {
initSqlUtil(invocation);
}
returnsqlUtil.processPage(invocation); }}Copy the code
Enter the sqlUtil. ProcessPage (invocation); methods
/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
private Object _processPage(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
Page page = null;
// When method arguments are supported, the Page is first tried
if (supportMethodsArguments) {
// Get Page information from thread-local variables, which we just set
page = getPage(args);
}
// Paging information
RowBounds rowBounds = (RowBounds) args[2];
// If page == null, there is no paging condition and no paging query is required
if ((supportMethodsArguments && page == null)
When paging parameters are not supported, determine LocalPage and RowBounds to determine whether paging is required| | (! supportMethodsArguments && SqlUtil.getLocalPage() ==null && rowBounds == RowBounds.DEFAULT)) {
return invocation.proceed();
} else {
// Page ==null when paging parameters are not supported
if(! supportMethodsArguments && page ==null) {
page = getPage(args);
}
// Enter to view
returndoProcessPage(invocation, page, args); }}Copy the code
/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
// Save RowBounds state
RowBounds rowBounds = (RowBounds) args[2];
// Get the original ms
MappedStatement ms = (MappedStatement) args[0];
// Determine and process as PageSqlSource
if(! isPageSqlSource(ms)) { processMappedStatement(ms); }// Set the current parser. The ThreadLocal value is set before each parser is used
((PageSqlSource)ms.getSqlSource()).setParser(parser);
try {
// Ignore RowBounds- otherwise it will paging Mybatis
args[2] = RowBounds.DEFAULT;
// If only sort or pageSizeZero judgment
if (isQueryOnly(page)) {
return doQueryOnly(page, invocation);
}
// Simply use the value of total to determine whether to run a count query
if (page.isCount()) {
page.setCountSignal(Boolean.TRUE);
/ / replace MS
args[0] = msCountMap.get(ms.getId());
// Query the total number
Object result = invocation.proceed();
/ / reduction of ms
args[0] = ms;
// Set the total number
page.setTotal((Integer) ((List) result).get(0));
if (page.getTotal() == 0) {
returnpage; }}else {
page.setTotal(-1l);
}
PageSize >0; pageSize<=0; pageSize<=0
if (page.getPageSize() > 0 &&
((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0) || rowBounds ! = RowBounds.DEFAULT)) {// Replace the MappedStatement argument with the new QS
page.setCountSignal(null);
// The point is to look at the method
BoundSql boundSql = ms.getBoundSql(args[1]);
args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
page.setCountSignal(Boolean.FALSE);
// Perform paging queries
Object result = invocation.proceed();
// Get the processing resultpage.addAll((List) result); }}finally {
((PageSqlSource)ms.getSqlSource()).removeParser();
}
// Return the result
return page;
}
Copy the code
Go to the BoundSql BoundSql = Ms. GetBoundSql (args[1]) method to trace to the PageStaticSqlSource class
@Override
protected BoundSql getPageBoundSql(Object parameterObject) {
String tempSql = sql;
String orderBy = PageHelper.getOrderBy();
if(orderBy ! =null) {
tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
}
tempSql = localParser.get().getPageSql(tempSql);
return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
}
Copy the code
You can also look at Oracle’s implementation of paging
So far we have found that the Implementation of PageHelper pagination turns out to be dynamically splicing SQL statements together with pagination statements before we execute SQL statements, thus achieving pagination fetched from the database.
Concern public number: Java baodian