Пейджинг Mybatis: Pagehelper + реализация перехватчика

MyBatis

1. Плагин пейджингаPagehelper

PageHelperдаMybatisПлагин пейджинга, очень простой в использовании!

1.1 Spring Bootполагаться

<!-- pagehelper 分页插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.12</version>
</dependency>

Его также можно импортировать

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>latest version</version>
</dependency>

1.2 PageHelperнастроить

добавлен файл конфигурацииPageHelperКонфигурация в основном задает диалект пейджинга и поддерживает параметры интерфейса для передачи параметров пейджинга следующим образом:

pagehelper:
  # 指定数据库
  helper-dialect: mysql
  # 默认是false。启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages(最大页数)会查询最后一页。禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据
  reasonable: false
  # 是否支持接口参数来传递分页参数,默认false
  support-methods-arguments: true
  # 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
  params: count=countSql
  row-bounds-with-count: true

Полный конфигурационный файл проекта подробно описан в текстеmybatis-pagehelper.

1.3 Как разбить на страницы

просто следуйтеPageHelper.startPageпервый после методаMybatisзапрос (Select) будет автоматически разбивать на страницы!!!!

@Test
public void selectForPage() {
    // 第几页
    int currentPage = 2;
    // 每页数量
    int pageSize = 5;
    // 排序
    String orderBy = "id desc";
    PageHelper.startPage(currentPage, pageSize, orderBy);
    List<UserInfoPagehelperDO> users = userInfoPagehelperMapper.selectList();
    PageInfo<UserInfoPagehelperDO> userPageInfo = new PageInfo<>(users);
    log.info("userPageInfo:{}", userPageInfo);
}
...: userPageInfo:PageInfo{pageNum=2, pageSize=5, size=1, startRow=6, endRow=6, total=6, pages=2, list=Page{count=true, pageNum=2, pageSize=5, startRow=5, endRow=10, total=6, pages=2, reasonable=false, pageSizeZero=false}[UserInfoPagehelperDO{id=1, userName='null', age=22, createTime=null}], prePage=1, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true, hasNextPage=false, navigatePages=8, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}

Возвращаемые результаты включают в себя данные, будь то первая/последняя страница, общее количество страниц и общее количество записей.Mybatis-PageHelper

Полный пример разбиения на страницы Pagehelper

два,MybatisПерехватчик реализует пейджинг

2.1 Mybatisперехватчик

Официальный сайт MybatisРаздел [Плагины] имеет следующее описание:

  1. пройти черезMyBatisМощный механизм, предусмотренный, используя плагины очень просты, просто реализуютInterceptorинтерфейс и укажите сигнатуру метода, который вы хотите перехватить.
  2. MyBatisПозволяет перехватывать вызовы в момент выполнения сопоставленного оператора. по умолчанию,MyBatisВызовы методов, которые разрешено перехватывать с помощью плагинов, включают:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

То есть: мы можем добиться этого с помощью перехватчиковMyBatisПлагин (разбивка данных на страницы)

Далее я сосредоточусь на демонстрации того, как я использовал перехватчики для реализации пейджинга.

2.2 Форма вызова

Прежде чем смотреть, как это реализовать, давайте посмотрим, как использовать:

копироватьPageHelperдизайн, сначала вызовите статический метод, для первого метода нижеsqlзаявление о перехвате, вnewАвтоматически обрабатывать при подкачке объекта.

@Test
public void selectForPage() {
    // 该查询进行分页,指定第几页和每页数量
    PageInterceptor.startPage(1,2);
    List<UserInfoDO> all = dao.findAll();
    PageResult<UserInfoDO> result = new PageResult<>(all);
    // 分页结果打印
    System.out.println("总记录数:" + result.getTotal());
    System.out.println(result.getData().toString());
}

Затем мы в основном рассмотрим этапы реализации.

2.3 Диалекты базы данных

Определите интерфейс диалекта и используйте разные диалекты для реализации разных данных.

  • Dialect.java
public interface Dialect {
    /**
     * 获取count SQL语句
     *
     * @param targetSql
     * @return
     */
    default String getCountSql(String targetSql) {
        return String.format("select count(1) from (%s) tmp_count", targetSql);
    }

    /**
     * 获取limit SQL语句
     * @param targetSql
     * @param offset
     * @param limit
     * @return
     */
    String getLimitSql(String targetSql, int offset, int limit);
}
  • MysqlДиалект пагинации
@Component
public class MysqlDialect implements Dialect{

    private static final String PATTERN = "%s limit %s, %s";

    private static final String PATTERN_FIRST = "%s limit %s";

    @Override
    public String getLimitSql(String targetSql, int offset, int limit) {
        if (offset == 0) {
            return String.format(PATTERN_FIRST, targetSql, limit);
        }

        return String.format(PATTERN, targetSql, offset, limit);
    }
}

2.4 Основная логика перехватчика

Полный код этой части см.PageInterceptor.java

  • Внутренний класс вспомогательного параметра пейджингаPageParam.java
public static class PageParam {
    // 当前页
    int pageNum;

    // 分页开始位置
    int offset;

    // 分页数量
    int limit;

    // 总数
    public int totalSize;

    // 总页数
    public int totalPage;
}
  • Запросить общее количество записей
private long queryTotal(MappedStatement mappedStatement, BoundSql boundSql) throws SQLException {

    Connection connection = null;
    PreparedStatement countStmt = null;
    ResultSet rs = null;
    try {

        connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();

        String countSql = this.dialect.getCountSql(boundSql.getSql());

        countStmt = connection.prepareStatement(countSql);
        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql,
                boundSql.getParameterMappings(), boundSql.getParameterObject());

        setParameters(countStmt, mappedStatement, countBoundSql, boundSql.getParameterObject());

        rs = countStmt.executeQuery();
        long totalCount = 0;
        if (rs.next()) {
            totalCount = rs.getLong(1);
        }

        return totalCount;
    } catch (SQLException e) {
        log.error("查询总记录数出错", e);
        throw e;
    } finally {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.error("exception happens when doing: ResultSet.close()", e);
            }
        }

        if (countStmt != null) {
            try {
                countStmt.close();
            } catch (SQLException e) {
                log.error("exception happens when doing: PreparedStatement.close()", e);
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                log.error("exception happens when doing: Connection.close()", e);
            }
        }
    }
}
  • ПагинацияSQLпараметр?установить значение
private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {
    ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler.setParameters(ps);
}
  • Заменить оригинальный диалектным интерфейсомSQLутверждение
private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
    MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

    builder.resource(ms.getResource());
    builder.fetchSize(ms.getFetchSize());
    builder.statementType(ms.getStatementType());
    builder.keyGenerator(ms.getKeyGenerator());
    if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
        StringBuffer keyProperties = new StringBuffer();
        for (String keyProperty : ms.getKeyProperties()) {
            keyProperties.append(keyProperty).append(",");
        }
        keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
        builder.keyProperty(keyProperties.toString());
    }

    //setStatementTimeout()
    builder.timeout(ms.getTimeout());

    //setStatementResultMap()
    builder.parameterMap(ms.getParameterMap());

    //setStatementResultMap()
    builder.resultMaps(ms.getResultMaps());
    builder.resultSetType(ms.getResultSetType());

    //setStatementCache()
    builder.cache(ms.getCache());
    builder.flushCacheRequired(ms.isFlushCacheRequired());
    builder.useCache(ms.isUseCache());

    return builder.build();
}
  • Подсчитать общее количество страниц
public int countPage(int totalSize, int offset) {
    int totalPageTemp = totalSize / offset;
    int plus = (totalSize % offset) == 0 ? 0 : 1;
    totalPageTemp = totalPageTemp + plus;
    if (totalPageTemp <= 0) {
        totalPageTemp = 1;
    }
    return totalPageTemp;
}
  • статический метод пагинации для вызова

Я разработал его здесь, количество страниц от1в начале, если вы привыкли0Для начала вы можете изменить его самостоятельно.

public static void startPage(int pageNum, int pageSize) {
    int offset = (pageNum-1) * pageSize;
    int limit = pageSize;
    PageInterceptor.PageParam pageParam = new PageInterceptor.PageParam();
    pageParam.offset = offset;
    pageParam.limit = limit;
    pageParam.pageNum = pageNum;
    PARAM_THREAD_LOCAL.set(pageParam);
}

2.5 Набор результатов пейджинга

Чтобы облегчить инкапсуляцию результатов, я инкапсулировал здесь относительно полный набор результатов разбиения по страницам, который содержит слишком много вещей.Давайте медленно рассмотрим следующие свойства (я думаю, что они более полны, добро пожаловать в лицо)

public class PageResult<T> implements Serializable {
    /**
     * 是否为第一页
     */
    private Boolean isFirstPage = false;
    /**
     * 是否为最后一页
     */
    private Boolean isLastPage = false;
    /**
     * 当前页
     */
    private Integer pageNum;
    /**
     * 每页的数量
     */
    private Integer pageSize;
    /**
     * 总记录数
     */
    private Integer totalSize;
    /**
     * 总页数
     */
    private Integer totalPage;
    /**
     * 结果集
     */
    private List<T> data;

    public PageResult() {
    }

    public PageResult(List<T> data) {
        this.data = data;
        PageInterceptor.PageParam pageParam = PageInterceptor.PARAM_THREAD_LOCAL.get();
        if (pageParam != null) {
            pageNum = pageParam.pageNum;
            pageSize = pageParam.limit;
            totalSize = pageParam.totalSize;
            totalPage = pageParam.totalPage;
            isFirstPage = (pageNum == 1);
            isLastPage = (pageNum == totalPage);
            PageInterceptor.PARAM_THREAD_LOCAL.remove();
        }
    }

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

    public Integer getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(Integer totalSize) {
        this.totalSize = totalSize;
    }

    public Integer getTotalPage() {
        return totalPage;
    }

    public void setTotalPage(Integer totalPage) {
        this.totalPage = totalPage;
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

    public Boolean getFirstPage() {
        return isFirstPage;
    }

    public void setFirstPage(Boolean firstPage) {
        isFirstPage = firstPage;
    }

    public Boolean getLastPage() {
        return isLastPage;
    }

    public void setLastPage(Boolean lastPage) {
        isLastPage = lastPage;
    }

    @Override
    public String toString() {
        return "PageResult{" +
                "isFirstPage=" + isFirstPage +
                ", isLastPage=" + isLastPage +
                ", pageNum=" + pageNum +
                ", pageSize=" + pageSize +
                ", totalSize=" + totalSize +
                ", totalPage=" + totalPage +
                ", data=" + data +
                '}';
    }
}

2.6 При простом тесте

@Test
public void selectForPage() {
    // 该查询进行分页,指定第几页和每页数量
    PageInterceptor.startPage(1,4);
    List<UserInfoDO> all = userMapper.findAll();
    PageResult<UserInfoDO> result = new PageResult<>(all);
    // 分页结果打印
    log.info("总记录数:{}", result.getTotalSize());
    log.info("list:{}", result.getData());
    log.info("result:{}", result);
}

Основное использование1.3Точно то же самое, только инкапсулированное в мой собственный набор результатов с разбивкой на страницы.

  • Журнал выглядит следующим образом:
....: ==>  Preparing: SELECT id, user_name, age, create_time FROM user_info_pageable limit 4 
....: ==> Parameters: 
....: <==      Total: 4
....: 总记录数:6
....: list:[UserInfoDO(id=1, userName=张三, age=22, createTime=2019-10-08T20:52:46), UserInfoDO(id=2, userName=李四, age=21, createTime=2019-12-23T20:22:54), UserInfoDO(id=3, userName=王二, age=22, createTime=2019-12-23T20:23:15), UserInfoDO(id=4, userName=马五, age=20, createTime=2019-12-23T20:23:15)]
....: result:PageResult{isFirstPage=true, isLastPage=false, pageNum=1, pageSize=4, totalSize=6, totalPage=2, data=[UserInfoDO(id=1, userName=张三, age=22, createTime=2019-10-08T20:52:46), UserInfoDO(id=2, userName=李四, age=21, createTime=2019-12-23T20:22:54), UserInfoDO(id=3, userName=王二, age=22, createTime=2019-12-23T20:23:15), UserInfoDO(id=4, userName=马五, age=20, createTime=2019-12-23T20:23:15)]}

При анализе журналов установлено, что общиеSELECT * FROM user_info_pageableсобран вSELECT * FROM user_info_pageable limit 4, указывающий, что пейджинг, реализованный перехватчиком, выполнен успешно.

3. Резюме

Два пути:PagehelperПейджинг и самостоятельная реализация, выберите его в соответствии с реальной ситуацией.

  1. PagehelperПример кода пагинации
  2. MybatisПерехватчик реализует пример кода подкачки

3.1 Ежедневная похвала

  1. читы семейной реликвииВесенняя коллекция подсолнуховОткрытый исходный код, добро пожаловать, чтобы жаловаться и давать подсказки!
  2. Магия Цзюян【Записная книжка по Java】Открытый исходный код, добро пожаловать, чтобы жаловаться и давать подсказки!

3.2 Культурный обмен

  1. Пыль Блог
  2. Блог Пыли - Самородки
  3. Пыль Блог - Блог Парк
  4. Пыль Блог — CSDN
  5. Github

Последняя статья, добро пожаловать на внимание: публичный аккаунт-блог fengchen; обмен мнениями, добро пожаловать на добавление: личный WeChat