Spring Boot + PageHelper realizes paging, which is a complete summary!

This article is contributed by the author "Chen Buer".

CSDN: https://blog.csdn.net/NOT_TWO_CHEN/article/details/109230267

Brief book: https://www.jianshu.com/p/a24a9ff323c9

I Development preparation

1. Development tools

  • IntelliJ IDEA 2020.2.3

2. Development environment

  • Red Hat Open JDK 8u256
  • Apache Maven 3.6.3

3. Development dependence

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

II Technical documentation

1. Based on SpringBoot

2. Based on MyBatis

3. Integrate PageHelper

III Application explanation

1. Basic use

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

Next is our most common usage:

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;
} 

To some extent, the above writing method is indeed in line with the usage specification of PageHelper:

	Use before collection query`PageHelper.startPage(pageNum,pageSize)`,also*Other operations cannot be interspersed in the middle SQL*

However, as developers, we can often find breakthroughs and opportunities only on the road of pursuing perfection and perfection;
The following are reasonable and standardized 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);
}

FAQ

1. Why declare a list function again?

A: in many practical business application scenarios, paging query is based on the table display requirements of large amount of data
However, many times, such as the mutual call of internal services and the provision of OpenAPI

Even in some business scenarios where the front and back ends are separated for joint debugging, a non paged collection query interface is also required to provide services
In addition, leaving aside the above factors for the time being, we can define and standardize some things according to the above writing method

For example, the separation and decoupling of paging and collection queries (see advanced usage for details of decoupling),
Separation of the request and response of paging request from the actual business parameters (see advanced use for details), etc

2. What is doselectpageinfo?

Answer: doSelectPageInfo is PageHelper The built-in Function of the default Page instance returned by the startpage() Function can be used to query through additional functions in the form of Lambda without unnecessary PageInfo and List conversion. The parameter of doSelectPageInfo is the Function(ISelect) interface built in PageHelper to achieve the purpose of PageInfo conversion

3. The amount of code written in this way seems to be a lot, but not much?

A: as described in ①, there is no further simplification in terms of code volume, but in some business scenarios, it is a more intuitive optimization when there is a list function interface (see advanced use for optimization details)

2. Advanced use

Look at the code first, and 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> {

    /**
     * Paging query
     *
     * @param param Request parameter DTO
     * @return Paging collection
     */
    default PageInfo<Result> page(PageParam<Param> param) {
        return PageHelper.startPage(param).doSelectPageInfo(() -> list(param.getParam()));
    }

    /**
     * Collection query
     *
     * @param param Query parameters
     * @return Query response
     */
    List<Result> list(Param param);
}

It can be seen that BaseService can be used as the encapsulation and declaration of the global Service general interface, while as the general paging interface, the page function directly declares the method body of the page function here by using the interface specific keyword default

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

@Data	// In order to omit redundant code, lombok should actually have conventional Getter/Setter Construction toString, etc
@Accessors(chain = true) // This lombok annotation is used to implement Entity pseudo Build, such as Entity setX(x). setY(y)
public class PageParam<T>  implements IPage {

   	//  description = "page number", DefaultValue = 1
    private Integer pageNum = 1;

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

    //	Description = "sort", example = "ID description"
    private String orderBy;

    //  description = "parameters"
    private T param;

    public PageParam<T> setOrderBy(String orderBy) {
        this.orderBy = orderBy; // You can optimize here. See the analysis for details
        return this;
    }
}

In BaseService, we see a new PageParam, which refers to PageInfo to wrap / declare / separate paging parameters and business parameters. The parameter type is generic, that is, it supports business parameters of any data type
At the same time, you can also see that PageParam implements the IPage interface and adds an orderBy attribute field

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

public interface TemplateService extends BaseService<TemplateReqDto, TemplateeRespDto> {
    // As the same interface, business services only need to inherit BaseService
    // And declare the Entity entity of request parameters and response results according to the actual use scenario
}

In practical application, 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	// Annotations for registering service beans in SpringBoot
@RequiredArgsConstructor	// Spring construction injection can be completed by generating constructors based on lombok according to all final attributes of the class
public class TemplateServiceImpl implements TemplateService {

    private final TemplateMapper mapper;

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

The implementation class only needs to rewrite the list method body to write the business logic processing and query methods that need to be processed in the actual business scenario, and does not need to care about the paging function

@Slf4j	// ditto
@RestController	// Annotation for registering Controller Bean in SpringBoot
@RequiredArgsConstructor	// ditto
public class TemplateController {

    public final TemplateService service;

    /**
     * Paging query
     *
     * @param pageParam Paging query parameters
     * @return Paging query response
     */
    @PostMapping(path = "page")
    public PageInfo<Result> page(@RequestBody PageParam<Param> pageParam) {
        return service.page(pageParam);
    }

    /**
     * Collection query
     *
     * @param listParam Set query parameters
     * @return Collection query response
     */
    @PostMapping(path = "list")
    public List<Result> list(@RequestBody Param listParam) {
        return service.list(listParam);
    }
}

Finally, when coding the Controller interface, you only need to call the service directly Page, and the request parameters are directly wrapped in PageParam to separate the paging parameters from the business parameters. In the joint debugging of front and rear interfaces, maintaining this separation specification can greatly reduce the communication and development cost

FAQ

1. Why can a page declare a method body when BaseService is used as an interface?

A: one of the new features in Java 8 is to add static/default methods to the interface class, that is, after declaring methods, their subclasses or implementations will have these methods by default and can be called directly
The reason for declaring default for the page method here is that the page function only focuses on paging parameters and paging response, which is separated from the business scenario and the method body is very different. Therefore, it is simply abstracted and defined to avoid the complex redundant process of its implementation

2. What is the meaning of the statement of pageparam? What is the purpose of implementing IPage?

A: PageParam is a class written with reference to PageInfo (I'm not sure whether PageHelper will encapsulate this class in the future. Maybe I can raise an Issue and also participate in the development of open source framework)
The purpose of writing this class is to separate paging and business data, so that developers can focus on business implementation and development. At the same time, it is also a specification of paging query API. Whether it is a request or response, the paging related data will be extracted and used separately
IPage is implemented because iPage, as the built-in interface of PageHelper, can be used as a specification for the declaration of paging parameters before we know more about its role, and iPage only declares three methods, namely the Getter method of pageNum/pageSize/orderBy. In addition, in the source code analysis, I will mention the deeper significance of implementing this interface

3. Why do you need an orderBy in addition to the conventional pageNum/pageSize in pageparam?

A: in the conventional paging query, only pageNum/pageSize is needed to complete the purpose of paging, but the paging query is often accompanied by filtering and sorting, while orderBy focuses on the dynamic parameter passing and sorting based on SQL

4. How to use orderby? Will there be any problem?

A: orderBy, like pageNum/pageSize, is injected into the query query by the Pagehelper through the MyBatis interceptor. Therefore, when passing parameters at the front end, the orderBy parameter should be in the form of database column desc/asc, and multi field sorting can be spliced with commas (,), such as columnA desc,columnB,
But on the other hand, there are two problems. First, in most database table field designs, snake case naming is used instead of hump case naming in conventional development. Therefore, there is a layer of conversion, and this conversion can be allocated to front-end parameter transmission and back-end parameter reception
The second is to expose the sorting field in the interface in such a way that there is a risk of order by SQL injection. Therefore, in the actual use process, we need to check and check whether the parameters passed by orderBy are legal through some means. For example, matching parameter values with regular expressions can only contain the necessary values in order by syntax, such as field name, desc or asc, and special characters / database keywords are not allowed

5. Does pagenum / PageSize have to be a default value?

A: by reading the source code of PageHelper, we know that when the Page query parameters are null, it will not give them default values and will not carry out additional processing, resulting in paging failure. The default values are also given to guard against various accidents that may occur in the process of debugging the front and rear interfaces

3. Source code analysis

First, let's look at PageHelper What happens during 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;
}

This is a static method in the abstract class PageMethod inherited by PageHelper (extend)

Look at the first line of code page < E > page = pageobjectutil What happened to getpagefromobject (params, true):

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 {
        ... // Here I only intercepted some code fragments, and the above is a more important one
	}
}

You can see that in this method, you will first judge whether params is null, and then judge whether it is a subclass or implementation class of IPage through instanceof
If the above two if/else are not satisfied, PageHelper will obtain pageNum/pageSize and orderBy through a large amount of reflection code in the code I omitted to post
As we all know, although reflection is widely used in Java, and as one of the unique features of the language, it is deeply loved by the majority of developers, to some extent, reflection requires performance cost. Even at this stage, many mainstream frameworks and technologies are trying to reduce the use of reflection to prevent the poor performance of the framework from being eliminated by the market
So far, we have finally explained and understood why PageParam needs to implement the IPage interface. In the code here, we can directly obtain the paging parameters through the interface without obtaining the parameters required by PageHelper through reflection that is detrimental to performance

Continue to look at 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() {
    }

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

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

You can see that the abstract class PageMethod inherited by PageHelper declares a thread local variable of Page, and getLocalPage() is to obtain the Page in the current thread
The next if (oldpage! = null & & oldpage. Isorderbyonly()) is to judge whether there is old paging data
The isOrderByOnly function here shows that it is true when only the orderBy parameter exists through the getPageFromObject() function
That is, when there is old paging data and the old paging data has only sorting parameters, the sorting parameters of the old paging data are included in the sorting parameters of the new paging data
Then save the new paging data page into the local thread variable
In practical application scenarios, this situation is still relatively rare, only sorting rather than paging, so from a certain point of view, we only need to understand

See what happens next in the info select Page:

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

It can be seen that the implementation of this method is very simple and clear, that is, through the registration and declaration of ISelect interface, the user-defined collection query method is developed and executed internally by it, and then the PageInfo entity is returned
We mentioned earlier that PageHelper achieves the purpose of paging based on MyBatis interceptor, so why Iselect Can the PageInfo entity be returned after doselect() is executed?
In fact, this is the wonderful use of interceptors, in select When doselect() is executed, it will trigger the MyBatis query interceptor customized by PageHelper, and page according to the database type by parsing SQL and SQL parameters, such as MySQL limit,Oracle Rownum, etc,
At the same time, before the query SQL defined by us, PageHelper will regenerate a select count(*) SQL and execute it first, which has achieved its purpose of defining Page built-in paging 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() {
    }

    public Object intercept(Invocation invocation) throws Throwable {
    ...
    ...
    }
}

The above is the built-in custom MyBatis interceptor in PageHelper. Due to the excessive amount of code, in order to ensure that it does not violate the principle of this blog, there is no unnecessary explanation here. If necessary, I can write a separate blog to explain and explain the concept and principle of MyBatis interceptor and deeply analyze the MyBatis source code

expand

PageHelper has not only pageNum/pageSize/orderBy parameters, but also pagesizezero and reasonable parameters for more advanced paging query definitions. For more in-depth understanding, I can write another advanced PageHelper for use. This article is only used for ordinary development

IV summary

As a 10K open source paging framework on GitHub, PageHelper may not be as deep and wide as the mainstream market framework and technology. Although it is not difficult to build wheels in terms of function implementation and principle, and the source code is very clear, it solves many paging technical problems based on MyBatis to a great extent, simplifies and prompts the efficiency of developers, This is the direction and path that developers should yearn for and strive for on the road of development
As beneficiaries, we should not only use it basically. In addition to development, we should also pay attention to the expansion of some frameworks, understand the bottom of the framework to a certain extent, and expand and optimize it

Put the open source repository of PageHelper here again!

https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter

I'm Chen Buer. Do you know me?

Recent hot article recommendations:

1.Java 15 officially released, 14 new features, refresh your understanding!!

2.Finally got the IntelliJ IDEA activation code through the open source project. It's really fragrant!

3.I wrote a paragraph of logic in Java 8. My colleagues can't understand it. Try it..

4.Hanging Tomcat, the performance of underwow is very explosive!!

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Posted by davey10101 on Mon, 09 May 2022 14:46:03 +0300