On Mybatis paging

1, Bicycles

Sometimes we may use our own business code to find out a List, and then use sublist for manual paging.

Manual paging can make it clear how to use the subList method of List, but this is very desirable. If the return value is too large, the memory is easy to be ruthlessly burst.

import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.*;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 */
public class TestMain03 {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // Get Mapper through sesson. This Mapper will program the proxy Mapper of Mybatis
            TestMapper mapper = session.getMapper(TestMapper.class);
            // Paging parameters passed in by business
            Integer pageNum = 1; // Current page (generally, if you interact with the front end, please accommodate them. The number of pages starts from 1)
            Integer pageSize = 10; // Size per page
            // Sometimes a third party or other interface may be called to query all data, and only all data is available
            // I also want to display in pages, but people don't provide you with a paging interface. This may be used at this time
            List<TestEntity> list = mapper.select();
            List<TestEntity> res = new ArrayList<>();
            if (list != null) {
                // Params:
                //fromIndex – low endpoint of the subList
                //toIndex – high endpoint of the subList
                // If yes: (fromindex < 0 | toindex > size | fromindex > toindex) throw exception IndexOutOfBoundsException –
                int size = list.size();
                // Prevent fromindex < 0
                if (pageNum <= 0) {
                    throw  new Exception("pageNum Must be an integer greater than or equal to 1");
                }
                if (pageSize <= 0) {
                    throw  new Exception("pageSize Must be an integer greater than or equal to 1");
                }
                int totalPage = (int) Math.ceil(Double.valueOf(size)/Double.valueOf(pageSize));
                // The number of pages exceeds the maximum number of pages (the front end is not trusted)
                if (pageNum > pageSize) {
                    res = new ArrayList<>();
                } else {
                    Integer fromIndex = (pageNum -1 ) * pageSize;
                    Integer toIndex =pageNum * pageSize;
                    // Prevent toindex > size
                    toIndex = toIndex > list.size() ? list.size() : toIndex;
                    res = list.subList(fromIndex, toIndex);
                }
            }
            for (int i = 0; i < res.size(); i++) {
                System.out.println(res.get(i));
            }
        }
    }
}

2, Motorcycles

Mybatis comes with RowBounds

After reading the source code, we know that it is actually a logical paging, which will also find out all the data. Then, an internal operation is carried out, first filter out the number of offset size, then continue to read the number of limit size, and finally return.

// If there are multiple parameters, add this parameter at the end 
@Select("select * from test")
List<TestEntity> selectRowBounds(RowBounds rd);
import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 */
public class TestMain04 {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // Get Mapper through sesson. This Mapper will program the proxy Mapper of Mybatis
            TestMapper mapper = session.getMapper(TestMapper.class);
            // The offset offset starts from 0, and the limit checks how many items in total
            Integer pageNum =1; // Current page (generally, if you interact with the front end, please accommodate them. The number of pages starts from 1)
            Integer pageSize = 10; // Size per page
            // If the paging parameter is wrong, it will not be judged first. Judge according to the business
            //
            RowBounds rd = new RowBounds((pageNum-1)*pageSize,pageSize);
            List<TestEntity> res = mapper.selectRowBounds(rd);
            for (int i = 0; i < res.size(); i++) {
                System.out.println(res.get(i));
            }
        }
    }
}

The source code implementation is simple, and the understanding is as follows:

// Cross offset records 
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
        if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
            rs.absolute(rowBounds.getOffset());
        }
    } else {
        // That is, rs.next(); Those who have written native sql know that this is
        for (int i = 0; i < rowBounds.getOffset(); i++) {
            rs.next();
        }
    }
}

// 
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    // Skip offset record
    skipRows(rsw.getResultSet(), rowBounds);
    // Read limit records from the remaining results 
    // Every time a record is read, it is given to resultContext++ 
    while (shouldProcessMoreRows(resultContext, rowBounds) 
           && rsw.getResultSet().next()) {
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
        Object rowValue = getRowValue(rsw, discriminatedResultMap);
        storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
}
// Here is to compare whether the number of reads is less than the limit. If it is less than the limit, it will be read circularly
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}

To put it bluntly, it is also processed through code logic

3, Manual transmission car

Physical paging, that is, the client will not be found. It has been divided on the server,

@Select("select * from test limit #{offset} , #{pageSize}")
List<TestEntity> selectLimit(@Param("offset") int offset, @Param("pageSize") Integer pageSize);
import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 */
public class TestMain05 {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // Get Mapper through sesson. This Mapper will program the proxy Mapper of Mybatis
            TestMapper mapper = session.getMapper(TestMapper.class);
            // The offset offset starts from 0, and the limit checks how many items in total
            Integer pageNum =1; // Current page (generally, if you interact with the front end, please accommodate them. The number of pages starts from 1)
            Integer pageSize = 10; // Size per page
            // If the paging parameter is wrong, it will not be judged first. Judge according to the business
            List<TestEntity> res = mapper.selectLimit((pageNum-1)*pageSize, pageSize);
            for (int i = 0; i < res.size(); i++) {
                System.out.println(res.get(i));
            }
        }
    }
}

Why is it a car? Because it consumes the least system memory and the least network. He will filter out unnecessary rows on the server.

4, Automatic transmission car

Someone said that it's boring to spell limit yourself. Can you be a little more intelligent? Of course, I can give it as long as you want.

Custom parameter base class PageQo:

package entity;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 * @create 2021-09-21 17:37
 */
public class PageQo {
    // Current number of pages: 1
    Integer pageNum;
    // Size per page
    Integer pageSize;

    public PageQo() {
    }
    public PageQo(Integer pageNum, Integer pageSize) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
    }

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}

Custom interceptor MyPageInterceptor:

package interceptor;

import entity.PageInfo;
import entity.PageQo;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Properties;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyPageInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("Execute interceptor code");
        // Get the parameter. It is specified here that paging will be performed only if the parameter is a subclass of PageQo, and other paging will not be performed
        StatementHandler  statementHandler = (StatementHandler) invocation.getTarget();
        //Gets the wrapper class of StatementHandler
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        BoundSql boundSql = statementHandler.getBoundSql();
        Object param = boundSql.getParameterObject();
        // Only when the parameter is a subclass of PageQo can paging be performed
        if(param instanceof PageQo) {
            // Forced conversion is mainly for paging parameters
            PageQo pageQo = (PageQo) param;
            //Get original SQL statement
            String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
            System.out.println("primary sql:"+originalSql);
            String sql = originalSql.trim() + " limit " + (pageQo.getPageNum()-1)* pageQo.getPageSize() + ", " + pageQo.getPageSize();
            System.out.println("paging sql:"+sql);
            metaObject.setValue("delegate.boundSql.sql", sql);
        }
        // Paging logic is not performed by default
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

Configuration file configuration interceptor mybatis-config.xml:

<?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>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <!--Scan package path-->
    <typeAliases>
        <package name="entity"/>
    </typeAliases>
    <!--custom interceptor -->
    <plugins>
        <plugin interceptor="interceptor.MyPageInterceptor"></plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--scanning-->
    <mappers>
        <mapper class="dao.TestMapper"/>
    </mappers>

</configuration>

Test:

import dao.TestMapper;
import entity.PageQo;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 */
public class TestMain06 {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // Get Mapper through sesson. This Mapper will program the proxy Mapper of Mybatis
            TestMapper mapper = session.getMapper(TestMapper.class);
            // The offset offset starts from 0, and the limit checks how many items in total
            Integer pageNum =1; // Current page (generally, if you interact with the front end, please accommodate them. The number of pages starts from 1)
            Integer pageSize = 10; // Size per page
            // If the paging parameter is wrong, it will not be judged first. Judge according to the business
            List<TestEntity> res = mapper.selectPage(new PageQo(pageNum, pageSize));
            for (int i = 0; i < res.size(); i++) {
                System.out.println(res.get(i));
            }
        }
    }
}

Output:

Mybatis interceptor is similar to our usual web project interceptors in that it is a mechanism that can be specially extended. Those who have studied the source code of SpringBoot know that in fact, it also has a similar mechanism called PostProcessor, which can customize their own business implementation

You can see that the original SQL has been modified, so this is also a server-side paging, physical paging.

5, Self driving car

PageHelper

He is an expert from the Internet. He has customized the Interceptor, which can be directly introduced and used

When we have the ability, we must provide people with more open source things (blogging is also a way of contribution)

Please pay attention to:

PageHelper.startPage() ; This must be followed by Mapper's calling method,

Introducing pom.xml

<!--pagehelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <!--520 It's nice to say to me: I love you-->
    <version>5.2.0</version>
</dependency>

Usage 1:

Like the custom Interceptor, mybatis-config.xml is introduced

<!--custom interceptor -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
    </plugin>
</plugins>
import com.github.pagehelper.PageHelper;
import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 */
public class TestMain07 {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // Get Mapper through sesson. This Mapper will program the proxy Mapper of Mybatis
            TestMapper mapper = session.getMapper(TestMapper.class);
            // The offset offset starts from 0, and the limit checks how many items in total
            Integer pageNum =1; // Current page (generally, if you interact with the front end, please accommodate them. The number of pages starts from 1)
            Integer pageSize = 10; // Size per page
            // 1. Most commonly used
            // https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md
            PageHelper.startPage(pageNum, pageSize);
            List<TestEntity> res = mapper.select();
            for (int i = 0; i < res.size(); i++) {
                System.out.println(res.get(i));
            }
        }
    }
}

Mode of use 2:

Custom pageNum and pageSize parameter names

mybatis-config.xml needs to be modified

<!--custom interceptor -->
<plugins>
    <!-- com.github.pagehelper by PageHelper Package name of class -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- Use the following method to configure parameters. All parameters will be described later -->
        <property name="supportMethodsArguments" value="true"/>
        <!--custom key There's nothing to do with a special value. Generally, we use it pageSize pageNum-->
        <property name="params" value="pageNum=myPageNum;pageSize=myPageSize;"/>
    </plugin>
</plugins>

Mapper

@Select("select * from test")
List<TestEntity> select01(@Param("myPageNum") Integer pageNum, @Param("myPageSize") Integer pageSize);

@Select("select * from test")
List<TestEntity> select02(Map<String, Integer> params);

Test

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Discover more interesting official account: Muzi's day and night programming
 * A code farmer who lives at the bottom of the Internet and does addition, deletion, modification and inspection, is not familiar with the affectation of the world
 */
public class TestMain08 {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // Get Mapper through sesson. This Mapper will program the proxy Mapper of Mybatis
            TestMapper mapper = session.getMapper(TestMapper.class);
            // The offset offset starts from 0, and the limit checks how many items in total
            Integer pageNum =1; // Current page (generally, if you interact with the front end, please accommodate them. The number of pages starts from 1)
            Integer pageSize = 10; // Size per page
            List<TestEntity> all = mapper.select();
            System.out.println("Total number of pieces:"+all.size());
            // 1. Parameter mode
            // https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md
            List<TestEntity> res = mapper.select01(pageNum, pageSize);
            System.out.println("Number of pages 01:"+res.size());
            // 2. Support ServletRequest, map and POJO objects,
            // request is difficult to obtain. Here is an example of Map
            Map<String, Integer> params = new HashMap<>();
            params.put("myPageNum", pageNum);
            params.put("myPageSize", pageSize);
            List<TestEntity> res02 = mapper.select02(params);
            System.out.println("Number of pages 02:"+res02.size());
            // You can see that the return type here is received with a list, but this list is not that list
            // In fact, he returned a com.github.pagehelper.Page, which inherited ArrayList
            // In addition to the data we see on this page, I think there are many advanced things
            // We can directly convert it to the PageInfo provided by him
            PageInfo pageInfo = new PageInfo(res02);
            System.out.println("Current number of pages pageNum:"+pageInfo.getPageNum());
            System.out.println("Page size pageSize:"+pageInfo.getPageSize());
            System.out.println("Previous page prePage:"+pageInfo.getPrePage());
            System.out.println("How many pages altogether:"+pageInfo.getPages());
            System.out.println("Number of current pages:"+pageInfo.getSize());
            System.out.println("Total number:"+pageInfo.getTotal());
            System.out.println("Number of record start lines:"+pageInfo.getStartRow());
            System.out.println("Record end lines:"+pageInfo.getEndRow());

            System.out.println("------------Gorgeous dividing line-----------------");
            // It's ok to use Page instead
            Page page = (Page)res02;
            System.out.println("Current number of pages pageNum:"+page.getPageNum());
            System.out.println("Page size pageSize:"+page.getPageSize());
            System.out.println("How many pages altogether:"+page.getPages());
            System.out.println("Number of current pages:"+page.size());
            System.out.println("Total number:"+page.getTotal());
            System.out.println("Number of record start lines:"+page.getStartRow());
            System.out.println("Record end lines:"+page.getEndRow());
        }
    }
}

Other usage:
It can also specify whether to use the count statement to count the total number of entries. My colleagues have encountered before. Because the count consumes a lot of performance, it is only counted once at the first query. If the front end of the query brings the count again, the count query will not be carried out here

6, Nagging

Some people think that PageHelper is very magical. Why does the method below know the page when startPage is called, and it can also get the page parameters.

Here, a practical knowledge point is called ThreadLocal

startPage actually puts the parameters in ThreadLocal. When you go to the Interceptor, you will find the corresponding parameters in ThreadLocal.

This knowledge point will be written in the future to briefly talk about the use of ThreadLocal. This knowledge point will also be used when the project needs to dynamically switch data sources.

There's nothing to do with the official account: Muzi's day and night programming.

Keywords: Java Database Mybatis

Added by langemarkdesign on Wed, 22 Sep 2021 01:21:39 +0300