SpringBoot integrates MyBatis paging

insert image description here

The goal of this article: SpringBoot integrates Mybatis paging in two ways, one is to use PageHelper to achieve it, and the other is to use interceptor paging.

MyBatis framework pagination implementation method:

  1. SQL paging , using the native sql keyword limit to achieve (not recommended)
  2. Use interceptor to splice sql to achieve the same function as limit (not recommended)
  3. Use PageHelper to achieve (simple)
  4. Interceptor paging (when the amount of data is large, it is necessary to implement the interceptor)

Note: The implementation of paging is based on the integration of MyBatis with SpringBoot.

One, pagehelper pagination

1. Add related dependencies

First, we need to add the paging plugin dependency package in the pom.xml file.

pom.xml

<!-- pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.5</version>
</dependency>

2. Add relevant configuration

Then add the configuration related to the pagination plugin in the application.yml configuration file.

application.yml

pagehelper:
    helperDialect: mysql
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql

3. Add pagination configuration

Paging query request encapsulation class

PageRequest.java

import lombok.Data;

@Data
public class PageRequest {
    
    
    // 当前页码
    private int pageNum;
    // 每页数量
    private int pageSize;
}

Paging query result encapsulation class

PageResult.java

import lombok.Data;
import java.util.List;

@Data
public class PageResult {
    
    
    // 当前页码
    private int pageNum;
    // 每页数量
    private int pageSize;
    // 记录总数
    private long totalSize;
    // 页码总数
    private int totalPages;
    // 数据模型
    private List<?> content;
}

Paging query related tool classes.

PageUtils.java

import com.github.pagehelper.PageInfo;

public class PageUtils {
    
    
    /**
     * 将分页信息封装到统一的接口
     * @param pageInfo
     * @return
     */
    public static PageResult getPageResult(PageInfo<?> pageInfo) {
    
    
        PageResult pageResult = new PageResult();
        pageResult.setPageNum(pageInfo.getPageNum());
        pageResult.setPageSize(pageInfo.getPageSize());
        pageResult.setTotalSize(pageInfo.getTotal());
        pageResult.setTotalPages(pageInfo.getPages());
        pageResult.setContent(pageInfo.getList());
        return pageResult;
    }
}

4. Add code

UserMapper.xml

<!--  查询分页  -->
<select id="getAllUserByPage" resultMap="BaseResultMap">
    <include refid="Base_Column_List" />
    from db_user
</select>

UserMapper.java

// 分页
List<User> getAllUserByPage();

service(IUserService.java && UserServiceImpl.java)

// IUserService
    /**
     * 分页查询接口
     * 这里统一封装了分页请求和结果,避免直接引入具体框架的分页对象, 如MyBatis或JPA的分页对象
     * 从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会
     * 影响服务层以上的分页接口,起到了解耦的作用
     * @param pageRequest 自定义,统一分页查询请求
     * @return PageResult 自定义,统一分页查询结果
     */
    PageResult getAllUserByPage(PageRequest pageRequest);
    
//  UserServiceImpl
    /** 分页查询 */
    @Override
    public PageResult getAllUserByPage(PageRequest pageRequest) {
    
    
        return PageUtils.getPageResult(getPageInfo(pageRequest));
    }

    /**
     * 调用分页插件完成分页
     * @param pageRequest
     * @return
     */
    private PageInfo<User> getPageInfo(PageRequest pageRequest) {
    
    
        int pageNum = pageRequest.getPageNum();
        int pageSize = pageRequest.getPageSize();
        PageHelper.startPage(pageNum, pageSize);
        List<User> sysMenus = userMapper.getAllUserByPage();
        return new PageInfo<User>(sysMenus);
    }

controller(UserController.java)

// pagehelper 分页 post 请求
@PostMapping("/findPage")
public Result findPage(@RequestBody PageRequest pageQuery) {
    
    
return Result.success(userService.getAllUserByPage(pageQuery));
}

5. Test

Test tool: postman

Test path: http://localhost:8081/findPage

Result graph:

img

2. Interceptor pagination

The implementation principle is mainly to modify the sql statement during the process of executing session query in the database, first query the total number of records, then query the data records in pages, and then integrate the data into pages.

img

1. Add relevant configuration

application-dev.yml

mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.mmdz.entity
# sql 打印
#  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  config-location: classpath:mybatis/mybatis-config.xml

Note: configuration should be commented, otherwise an error will be reported IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together.

// 配置重复导致冲突

Caused by: java.lang.IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together
 at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.1.jar:5.3.1]
 at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:488) ~[mybatis-spring-2.0.6.jar:2.0.6]
    at org.mybatis.spring.SqlSessionFactoryBean.getObject(SqlSessionFactoryBean.java:633) ~[mybatis-spring-2.0.6.jar:2.0.6]

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="cacheEnabled" value="false" /><!-- 暂时禁用缓存 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/><!-- 打印sql-->
    </settings>
    <plugins>
        <plugin interceptor="com.mmdz.common.interceptor.PaginationInterceptor"></plugin>
    </plugins>
</configuration>

2. Add interceptor code and configuration

PaginationInterceptor.java

**Note:** This class intercepts the prepare method of the StatementHandler class, and parses and obtains the request method parameters. If it is a single parameter, and the parameter type is SimplePage, the pagination mode is adopted. Or there are multiple parameters, and one of the parameters is marked as page (that is, @Param("page") in the interface), and the paging mode is also enabled. Among them, the getCountSql(String sql) method and getPageSql(String sql, SimplePage page) method need to be modified according to different databases. I use Mysql.

import com.mmdz.common.interceptor.page.SimplePage;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts(
        {
    
     @Signature(type = StatementHandler.class,
                method = "prepare", args = {
    
     Connection.class , Integer.class}) })
public class PaginationInterceptor implements Interceptor {
    
    

    private static final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    private static String dialect = "mysql";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        // 获得拦截的对象
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 待执行的sql的包装对象
        BoundSql boundSql = statementHandler.getBoundSql();
        // 判断是否是查询语句
        if (isSelect(boundSql.getSql())) {
    
    
            // 获得参数集合
            Object params = boundSql.getParameterObject();

            if (params instanceof Map) {
    
     // 请求为多个参数,参数采用Map封装
                return complexParamsHandler(invocation, boundSql, (Map<?, ?>) params);
            } else if (params instanceof SimplePage) {
    
     // 单个参数且为Page,则表示该操作需要进行分页处理
                return simpleParamHandler(invocation, boundSql, (SimplePage) params);
            }
        }
        return invocation.proceed();
    }

    private Object complexParamsHandler(Invocation invocation, BoundSql boundSql, Map<?, ?> params) throws Throwable {
    
    
        //判断参数中是否指定分页
        if (containsPage(params)) {
    
    
            return pageHandlerExecutor(invocation, boundSql, (SimplePage) params.get("page"));
        } else {
    
    
            return invocation.proceed();
        }
    }

    private boolean containsPage(Map<?, ?> params) {
    
    
        if(params==null){
    
    
            return false;
        }else if(!params.containsKey("page")){
    
    
            return false;
        }
        Object page = params.get("page");
        if(page==null){
    
    
            return false;
        }else if(page instanceof SimplePage){
    
    
            return true;
        }
        return false;
    }

    private boolean isSelect(String sql) {
    
    
        if (!StringUtils.isEmpty(sql) && sql.toUpperCase().trim().startsWith("SELECT")) {
    
    
            return true;
        }
        return false;
    }

    private Object simpleParamHandler(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable {
    
    
        return pageHandlerExecutor(invocation, boundSql, page);
    }

    private Object pageHandlerExecutor(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable {
    
    
        // 获得数据库连接
        Connection connection = (Connection) invocation.getArgs()[0];
        // 使用Mybatis提供的MetaObject,该对象主要用于获取包装对象的属性值
        MetaObject statementHandler = MetaObject.forObject(invocation.getTarget(), DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);

        // 获取该sql执行的结果集总数
        int maxSize = getTotalSize(connection, (MappedStatement) statementHandler.getValue("delegate.mappedStatement"),
                boundSql);

        // 生成分页sql
        page.setTotalRecord(maxSize);
        String wrapperSql = getPageSql(boundSql.getSql(), page);

        MetaObject boundSqlMeta = MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
                DEFAULT_REFLECTOR_FACTORY);
        // 修改boundSql的sql
        boundSqlMeta.setValue("sql", wrapperSql);
        return invocation.proceed();
    }

    private int getTotalSize(Connection connection, MappedStatement mappedStatement, BoundSql boundSql) {
    
    
        String countSql = getCountSql(boundSql.getSql());
        PreparedStatement countStmt;
        ResultSet rs;
        List<AutoCloseable> closeableList = new ArrayList<AutoCloseable>();

        try {
    
    
            countStmt = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
            rs = countStmt.executeQuery();

            if (rs.next()) {
    
    
                return rs.getInt(1);
            }
            closeableList.add(countStmt);
            closeableList.add(rs);
        } catch (SQLException e) {
    
    
            logger.error("append an exception[{}] when execute sql[{}] with {}", e, countSql,
                    boundSql.getParameterObject());
        } finally {
    
    
            for (AutoCloseable closeable : closeableList) {
    
    
                try {
    
    
                    if (closeable != null)
                        closeable.close();
                } catch (Exception e) {
    
    
                    logger.error("append an exception[{}] when close resource[{}] ", e, closeable);
                }
            }
        }
        return 0;
    }

    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {
    
    
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }

    @Override
    public Object plugin(Object target) {
    
    
        // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
    
    
            return Plugin.wrap(target, this);
        } else {
    
    
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
    
    

    }

    public String getCountSql(String sql) {
    
    
        if("mysql".equals(dialect)){
    
    
            return "select count(0) from (" + sql + ") as total";
        }
        return sql;
    }

    public String getPageSql(String sql, SimplePage page) {
    
    
        if(page.getPage()<=0){
    
    
            page.setPage(1);
        }
        if(page.getRows()<=0){
    
    
            page.setRows(20);
        }
        int startRow = (page.getPage()-1)*page.getRows();

        if(startRow>=page.getTotalRecord()){
    
    
            page.setPage(1);
            startRow=0;
        }
        if("mysql".equals(dialect)){
    
    
            return sql+" limit "+startRow+", "+page.getRows();
        }
        return sql;
    }
}

SimplePage.java

**Note:** This class is the simplest class required by the paging object. As long as it includes the page number, the number of items per page, the total number of items and data records, all the required paging parameters can be calculated. So use this class to judge the parameters of the interceptor. If you need more page number information, you can rewrite a paging class to inherit the SimplePage.

import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;

@Getter
@Setter
public class SimplePage implements Serializable {
    
    

    protected static final long serialVersionUID = 5136213157391895517L;

    protected int page = 1;// 页码,默认是第一页
    protected int rows = 10;// 每页显示的记录数,默认是10
    protected int totalRecord;// 总记录数
    protected List data;// 当前页记录

    public SimplePage setData(List data) {
    
    
        this.data = data;
        return this;
    }
}

Page.java

**Note:** This Page class is mainly to enrich the SimplePage class, the most important is the setData (List data) method, which is used to enrich some variable data.

import lombok.Getter;
import lombok.Setter;
import java.util.List;

@Getter
@Setter
public class Page extends SimplePage {
    
    
    private static final long serialVersionUID = -6190845403265328029L;

    private boolean isFirstPage = true;//是否是第一页
    private boolean isLastPage = false;//是否是最后一页
    private int pageCount = 0;//当前页总记录数
    private int totalPage = 0;//总页数
    private int prePage = 1;//上一页页码
    private int nextPage = 1;//下一页页码

    public Page() {
    
    
        super();
    }

    public Page(int page, int rows) {
    
    
        super();
        setPage(page);
        setRows(rows);
    }

    @Override
    public Page setData(List data){
    
    
        super.setData(data);
        if(data!=null && data.size()>0){
    
    
            pageCount = data.size();
            if(this.page==1){
    
    
                isFirstPage=true;
            }else{
    
    
                isFirstPage=false;
            }
            //***
            totalPage = (int)Math.ceil(totalRecord/(double)rows);
            //***
            if(page==totalPage){
    
    
                isLastPage = true;
            }else{
    
    
                isLastPage = false;
            }
            //***
            if(isFirstPage){
    
    
                prePage = 1;
            }else{
    
    
                prePage = page-1;
            }
            //***
            if(isLastPage){
    
    
                nextPage = 1;
            }else{
    
    
                nextPage = page+1;
            }
        }else{
    
    
            isLastPage = true;
        }
        return this;
    }
}

3. Add code

UserMapper.xml

<select id="findPage" resultMap="BaseResultMap">
    <include refid="Base_Column_List" />
    from db_user
</select>

UserMapper.java

**Note:** If this query method only has the paging parameter page and no other parameters, @Param("page") may not be written. If there are multiple parameters, @Param must be used to indicate the parameter name. This is the interceptor to judge paging basis.

/** @Param("page")是应分页插件要求编写的 */
List<User> findPage(@Param("page") Page page);

UserServiceImpl.java

/**
* 拦截器分页
* @param page
* @return
*/
@Override
public Page findPage(Page page) {
    
    
    List<User> list = userMapper.findPage(page);
    page.setData(list);
    return page;
}

IUserService.java

/**
* 拦截器分页
* @param page
* @return
*/
Page findPage(Page page);

UserController.java

// 拦截器 分页 post 请求
@PostMapping("/findPage2")
public Result findPage(@RequestBody Page page){
    
    
    return Result.success(userService.findPage(page));
}

4. Test

Test tool: postman

Test path: http://localhost:8081/findPage2

Result graph:

pl.java**

/**
* 拦截器分页
* @param page
* @return
*/
@Override
public Page findPage(Page page) {
    
    
    List<User> list = userMapper.findPage(page);
    page.setData(list);
    return page;
}

IUserService.java

/**
* 拦截器分页
* @param page
* @return
*/
Page findPage(Page page);

UserController.java

// 拦截器 分页 post 请求
@PostMapping("/findPage2")
public Result findPage(@RequestBody Page page){
    
    
    return Result.success(userService.findPage(page));
}

4. Test

Test tool: postman

Test path: http://localhost:8081/findPage2

Result graph:

img

Guess you like

Origin blog.csdn.net/qq_51808107/article/details/131393594