SpringBoot整合Mybatis分頁


SpringBoot整合Mybatis分頁

?> 學習目的: 分頁的好處就是減少數據的處理量

mybatis框架分頁實現,常用得幾種方式,最簡單的就是

  1. sql分頁,利用原生的sql關鍵字limit來實現;(不推薦)

  2. 還有一種就是利用interceptor來拼接sql,實現和limit一樣的功能;(不推薦)

  3. 再一個就是利用PageHelper來實現。這里講解這三種常見的實現方式 。

  4. 攔截器分頁。( 數據量大時,實現攔截器就很有必要了)

!> 注意:分頁的實現,是基於 SpringBoot整合MyBatis 之上。


1、pagehelper 分頁

添加相關依賴

首先,我們需要在 pom.xml 文件中添加分頁插件依賴包。

pom.xml

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

添加相關配置

然后在 application-dev.yml 配置文件中添加分頁插件有關的配置。

application-dev.yml

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

添加分頁配置

分頁查詢請求封裝類。

PageRequest.java

import lombok.Data;
​
@Data
public class PageRequest {
    /**當前頁碼*/
    private int pageNum;
    /**每頁數量*/
    private int pageSize;
}

分頁查詢結果封裝類。

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

分頁查詢相關工具類。

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

添加代碼

UserMapper.xml

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

dao(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));
    }

測試

測試工具:postman(自己用自己喜歡的工具)

測試路徑: http://localhost:8081/findPage

結果圖:


2、攔截器分頁

實現原理主要是在數據庫執行session查詢的過程中,修改sql語句,先查詢記錄總條數,然后再分頁查詢數據記錄,再把數據整合成分頁數據形式就可以了。

添加相關配置

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

注意:要注釋 configuration ,否則會報錯 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>

添加攔截器代碼和配置

PaginationInterceptor.java

注意:這個類攔截StatementHandler類的prepare方法,解析獲取請求方法參數。如果是單個參數,且參數類型為SimplePage,則采用分頁模式。或者有多個參數,並且其中一個參數被標注為page(即接口中@Param("page")),也啟用分頁模式。其中getCountSql(String sql)方法和getPageSql(String sql, SimplePage page)方法需要根據不同數據庫進行修改,我用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;
​
/**
 * @Author: MMDZ
 * @Date: 2021/5/31
 * @Desc: 分頁攔截器mybatis
 */
@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

注意:這個類是分頁對象所需的最簡單的類,只要包含頁碼、每頁條數、總條數和數據記錄,就可以推算出所有需要的分頁參數。所以用這個類來給攔截器做參數判斷,若需要更多的頁碼信息可以重寫一個分頁類繼承這個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

注意:這個Page類主要是豐富SimplePage類,最重要的就是 setData(List data)方法,由這個方法來豐富一些變量數據。

import lombok.Getter;
import lombok.Setter;

import java.util.List;

/**
 * @Author: MMDZ
 * @Date: 2021/5/31
 * @Desc: 這個Page類主要是豐富SimplePage類,最重要的就是 setData(List data)方法,
 *                  由這個方法來豐富一些變量數據。
 */
@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;
    }
}

添加代碼

UserMapper.xml

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

UserMapper.java

注意:如果這個查詢方法只有分頁參數page,沒有別的參數,可以不寫@Param("page"),若有多個參數必須用@Param標明參數名,這是攔截器判斷分頁的依據。

   /** @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));
    }

測試

測試工具:postman(自己用自己喜歡的工具)

測試路徑: http://localhost:8081/findPage2

結果圖:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM