PageHelper

I. Development preparation

Original logo

1. Development tools

  • IntelliJ IDEA 2020.2.3

2. Development environment

  • Red Hat Open JDK 8u256
  • Apache Maven 3.6.3

3. Develop dependencies

  • SpringBoot
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code
  • MyBatis
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.3</version>
</dependency>
Copy the code
  • PageHelper
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>1.3.0</version>
</dependency>
Copy the code

Technical documentation

Original logo

1. Based on SpringBoot

  • Official SpringBoot document
  • SpringBoot Chinese Community

2. Based on MyBatis

  • MyBatis official document

3. The integrated PageHelper

  • PageHelper open source repository

Three. Application explanation

Original logo

1. Basic use

In the application of actual projects, the use of PageHelper is very convenient and fast. Only PageInfo + PageHelper two classes are enough to complete the paging function. However, this simplest integrated use method has not been fully developed and utilized in many practical application scenarios.

Here’s how we use it most often:

public PageInfo<ResponseEntityDto> page(RequestParamDto param) {
	PageHelper.startPage(param.getPageNum(), param.getPageSize());
	List<ResoinseEntityDto> list = mapper.selectManySelective(param);
	PageInfo<ResponseEntityDto> pageInfo = (PageInfo<ResponseEntityDto>)list;
	return pageInfo;
}
Copy the code

To some extent, this is the way PageHelper works:

Use pageHelper. startPage(pageNum,pageSize) before collection query, and do not intersperse other SQL

But as a Developer, we often only in the pursuit of perfection and the ultimate road to find breakthroughs and opportunities; The following are reasonable and standard basic uses:

public PageInfo<ResponseEntityDto> page(RequestParamDto param) {
	return PageHelper.startPage(param.getPageNum(), param.getPageSize())
			  .doSelectPageInfo(() -> list(param))
}
public List<ResponseEntityDto> list(RequestParamDto param) {
	return mapper.selectManySelective(param);
}
Copy the code

FAQ

1. Why redeclare a list function?

A: In many practical business application scenarios, paging queries are based on the need to display tables with large amounts of data.

However, many times, such as internal service calls to each other,OpenAPI provides.

Even in some business scenarios, a non-paged collection query interface is required to provide services.

In addition, despite the above factors for the time being, we can define and standardize something according to the above writing

For example, the decoupling and decoupling of paging and collection queries (see advanced use for decoupling details),

The separation of the request and response of paging requests from the actual business parameters (see advanced use for details) and so on…

2. doSelectPageInfoWhat is?

A: doSelectPageInfo is a function built into the default Page instance returned by pageHelper.startPage ()

This Function can be used as a Lambda with additional Function queries without the need for PageInfo and List conversions

The argument to doSelectPageInfo is the built-in Function(ISelect) interface for PageHelper to convert PageInfo

3. How much code does this look like?

A: As described in ①, there is really no further simplification in terms of the amount of code

However, in some business scenarios where the list function interface is already available, it is a more intuitive optimization (see advanced use for optimization details).

Original logo

2. Advanced use

First look at the code, then talk about parsing:

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import java.util.List;

/ * * *@param<Param> Generic request *@param<Result> Generic response */
public interface BaseService<Param.Result> {
    /** ** *@paramParam Request parameter DTO *@returnPaging collection */
    default PageInfo<Result> page(PageParam<Param> param) {
        return PageHelper.startPage(param).doSelectPageInfo(() -> list(param.getParam()));
    }
    /** * set query **@paramParam Query parameter *@returnQuery response */
    List<Result> list(Param param);
}
Copy the code

You can see that BaseService can be used as a wrapper and declaration for the global Service generic interface

The page function, as a general paging interface, uses the interface keyword default to declare the body of the page function directly

import com.github.pagehelper.IPage;
import lombok.Data;
import lombok.experimental.Accessors;

@Data	// Using Lombok to omit redundant code actually requires regular getters/setters Construction toString, etc
@Accessors(chain = true) // This Lombok annotation is used to implement Entity pseudo-builds such as entity.setx (x).sety (y).
public class PageParam<T>  implements IPage {

   	// description = "page ", defaultValue = 1
    private Integer pageNum = 1;

    // description = "pages ", defaultValue = 20
    private Integer pageSize = 20;

    Example = "id desc"
    private String orderBy;

    // description = "parameter"
    private T param;

    public PageParam<T> setOrderBy(String orderBy) {
        this.orderBy = orderBy; // You can optimize it here
        return this; }}Copy the code

In BaseService we see a new PageParam that references PageInfo to wrap/declare/separate paging parameters and business parameters. The parameter type is generic, i.e. business parameters that support any data type

You can also see that PageParam implements the IPage interface and has an orderBy property field

import common.base.BaseService;
import dto.req.TemplateReqDto;
import dto.resp.TemplateRespDto;

public interface TemplateService extends BaseService<TemplateReqDto.TemplateeRespDto> {
    // For the same interface, the Service only needs to inherit from BaseService
    // Specify the Entity Entity of the request parameters and response result based on the actual usage scenario
}
Copy the code

In practice, we only need to declare our common business query request parameters and response results

import dto.req.TemplateReqDto;
import dto.resp.TemplateRespDto;
import service.TemplateService;
import persistence.mapper.TemplateMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j // Automatically generate logger logging instances based on Lombok
@Service	// Register Service Bean annotations in SpringBoot
@RequiredArgsConstructor	// Spring constructor injection can be done by generating constructors based on lombok for all final properties of the class
public class TemplateServiceImpl implements TemplateService {

    private final TemplateMapper mapper;

    @Override
    public List<TemplateRespDto> list(TemplateReqDto param) {
        return mapper.selectManySelective(param) // Entities can be converted according to the actual situation}}Copy the code

In the implementation class, you only need to rewrite the list method body to write the business logic processing and query methods that need to be handled in the actual business scenario. You don’t need to care about paging

@Slf4j	/ / same as above
@RestController	// Register Controller Bean annotations in SpringBoot
@RequiredArgsConstructor	/ / same as above
public class TemplateController {

    public final TemplateService service;

    /** ** *@paramPageParam Page query parameter *@returnPaging query response */
    @PostMapping(path = "page")
    public PageInfo<Result> page(@RequestBody PageParam<Param> pageParam) {
        return service.page(pageParam);
    }

    /** * set query **@paramListParam Collection query parameter *@returnSet query response */
    @PostMapping(path = "list")
    public List<Result> list(@RequestBody Param listParam) {
        returnservice.list(listParam); }}Copy the code

Finally, when coding Controller interface, it only needs to directly call service.page, and request parameters are packaged directly with PageParam to separate paging parameters from business parameters. In the joint tuning of front and back interfaces, maintaining this separation standard can greatly reduce communication and development costs

Original logo

FAQ

1. BaseServiceAs ainterface.pageWhy can a method body be declared?

A: one of the new features in Java8 is the addition of static/default methods to the interface interface class. That is, after a method is declared, its subclasses or implementations will have these methods by default and can be called directly

The reason why we declare default for the Page method here is that the Page function only focuses on paging parameters and paging responses, which deviates from the business scenario and has a very different method body. Therefore, we simply define it abstractly to avoid the complex and redundant process of its implementation

2. PageParamWhat is the significance of the statement? implementationIPageFor what?

A: PageParam is a class written by reference to PageInfo (not sure if PageHelper will encapsulate this class in the future, maybe I can make an Issue and participate in the development of open source framework)

The purpose of this class is to separate paging from business data and allow developers to focus on business implementation and development. It is also a specification of the paging query API that separates page-related data from requests and responses

And the implementation of IPage is because IPage as a built-in interface of PageHelper, in the do not understand its more meaningful role, can be used as a specification of our paging parameter declaration, and IPage only declared three methods, respectively is pageNum/pageSize/orderBy Getter methods, and in the source code analysis, I’ll cover the deeper meaning of implementing this interface

3. PageParamIn addition to the conventionalpageNum/pageSizeWhy do you need another oneorderBy?

A: In a regular paging query, only pageNum/pageSize is required to accomplish the purpose of paging, but often there is a filter sort along with paging query

Order Derby, on the other hand, focuses on SQL-based dynamic parameter sorting

4. orderByHow to use it? Will there be any problems?

A: OrderBy, like pageNum/pageSize, is injected into query by Pagehelper through MyBatis interceptor, so the orderBy parameter should be the database column in front of the parameter ColumnA desc,columnB, columnA desc,columnB,

However, on the other hand, there are two problems. First, most database table field designs use the == snake case== name, instead of the == camel case== name in unconventional development, so there is a layer of transformation, and this transformation can be assigned to the front-end parameter passing, and can be assigned to the back-end parameter.

The second is that exposing the sorting field to the interface in such a naked way will lead to the risk of ORDER by SQL injection. Therefore, in the actual use process, we need to verify and check whether Derby parameters are valid by some means. For example, regular expression matching parameter values can only contain ORDER Necessary values in the by syntax, such as field name,desc or ASc, no special characters/database keywords allowed, etc

5. pageNum/pageSizeDo I have to give a default value?

A: Reading the source code of PageHelper, we know that when Page query parameters are null, it does not give them default values. It does not do any extra processing, so that pagination fails. It also gives default values to guard against all kinds of accidents that may occur during the debugging of the front and back end interface

3. Source code analysis

First let’s look at what happens to pageHelper.startPage (param) :

public static <E> Page<E> startPage(Object params) {
	Page<E> page = PageObjectUtil.getPageFromObject(params, true);
	Page<E> oldPage = getLocalPage();
	if(oldPage ! =null && oldPage.isOrderByOnly()) {
		page.setOrderBy(oldPage.getOrderBy());
	}
	setLocalPage(page);
	return page;
}
Copy the code

This is a static method in the Abstract PageMethod class of PageHelper inheritance (Extend)

Again on the first line of code Page < E > Page. = PageObjectUtil getPageFromObject (params, true) what happened:

public static <T> Page<T> getPageFromObject(Object params, boolean required) {
	if (params == null) {
		throw new PageException("Unable to get paging query parameters!");
	} else if (params instanceof IPage) {
		IPage pageParams = (IPage)params;
		Page page = null;
		if(pageParams.getPageNum() ! =null&& pageParams.getPageSize() ! =null) {
			page = new Page(pageParams.getPageNum(), pageParams.getPageSize());
		}
		if (StringUtil.isNotEmpty(pageParams.getOrderBy())) {
			if(page ! =null) {
				page.setOrderBy(pageParams.getOrderBy());
			} else {
				page = new Page();
				page.setOrderBy(pageParams.getOrderBy());
				page.setOrderByOnly(true); }}return page;
	} else{...// I have only captured some code snippets here, the above is the more important one}}Copy the code

As you can see in this method, params is null and instanceof is a subclass or implementation of IPage if/else If not, PageHelper gets pageNum/pageSize and orderBy. Through a lot of reflection code in the code I’ve omitted to post. Became known, although widely used in Java, as one of the unique characteristics of language, and loved by the majority of developers, but reflect to some extent, is the need to cost performance and even many mainstream framework and technology, at present stage is to minimize the use of reflection, in order to prevent the framework had poor performance, was eliminated by the market. PageParam implements the IPage interface, which allows you to retrieve pagination parameters directly from the interface, rather than the need to retrieve PageHelper parameters through performance – poor reflection

Continue with the following code in startPage:

public abstract class PageMethod {
    protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
    protected static boolean DEFAULT_COUNT = true;

    public PageMethod(a) {}protected static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }

    public static <T> Page<T> getLocalPage(a) {
        return(Page)LOCAL_PAGE.get(); }... . }Copy the code

You can see that PageHelper inherits an abstract class PageMethod that declares a thread-local variable for a Page, while getLocalPage() gets the Page in the current thread

And then if (oldPage! = null && oldPage.isOrderByOnly()) determines whether old paging data exists

The isOrderByOnly here is true if only the orderBy argument exists, as we can see from the getPageFromObject() function

That is, when old paging data exists and the old paging data has only sort parameters, the sort parameters of the old paging data are included in the sort parameters of the new paging data

The new paging data page is then stored in a local thread variable

In the real world, this is still rare, sorting only, not pagination, so in a way, we just need to know

Original logo

Now look at what happens in doSelectPageInfo(ISelect) :

public <E> PageInfo<E> doSelectPageInfo(ISelect select) {
	select.doSelect();
	return this.toPageInfo();
}
Copy the code

As you can see, the implementation of this method is straightforward: develop a custom collection query method and execute it internally by registering the ISelect interface, and then return the PageInfo entity

PageHelper () returns PageInfo from iselec.doSelect ()

When select.doSelect() is executed, it will trigger MyBatis query interceptor, and by parsing SQL and SQL parameters, according to the database type, pagination, such as MySQL limit,Oracle Rown Um, etc.

PageHelper also generates a select Count (*) SQL before we define the query SQL, which has achieved the purpose of defining the built-in Page Page parameters

@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} )})
public class PageInterceptor implements Interceptor {
    private volatile Dialect dialect;
    private String countSuffix = "_COUNT";
    protected Cache<String, MappedStatement> msCountMap = null;
    private String default_dialect_class = "com.github.pagehelper.PageHelper";

    public PageInterceptor(a) {}public Object intercept(Invocation invocation) throws Throwable {... . }}Copy the code

The above is PageHelper built-in custom MyBatis interceptor, because of too much code, in order to ensure that not violate the principle of the irrelevant topic, here no longer do redundant explanation, if necessary, I can write a separate blog to explain and explain the concept and principle of MyBatis interceptor, in-depth analysis of MyBatis source code

expand

PageHelper not only has pageNum/pageSize/orderBy parameters, but also pageSizeZero, A reasonable parameter is used for more advanced paging query definitions. If you need to know more about it, I can write a separate page for advanced ==PageHelper==. This article is only for common development

Original logo

4. To summarize

PageHelper, as a nearly 10K open source paging framework on GitHub, may not be as deep and broad as the mainstream market framework and technology. Although in the realization and principle of functions, the difficulty of making wheels is not high, the source code is also very clear, but to a large extent, it solves a lot of problems based on MyBatis paging technology, simplifying and suggesting a wide range The efficiency of great developers is the direction and path that developers should aspire to and strive for on the road of development. As the beneficiaries, we should not only use the basic framework, but also pay attention to the expansion of some framework, have a certain level of understanding of the bottom of the framework, and expand and optimize it

Here again is the Open source repository for PageHelper!

I am Chen not two, you, know me?