mybatis 的插件应用:分页处理(分页插件PageHelper)

mybatis 框架虽然使用起来相当简单,且灵活,自己管理着 sql 语句,但是开发起来还是有着不少的工作量,比如在处理分页问题的时候,我们通常需要另外再查询一次总共的记录数

其实我们是希望把获取分页信息的工作给统一起来,简洁代码,减少工作量

我们可以利用 mybatis 的插件功能(拦截器)来处理分页,这里我们尝试着自己去写一个简单的分页插件,后面也有介绍一个优秀的 mybatis 分页插件 PageHelper 的使用方法

一、自己写一个简单的分页插件

在 mybatis xml 配置文件中,我们可以配置插件(plugins),mybatis 允许你在已映射语句执行过程中的某一点进行拦截调用,这样我们可以通过拦截查询方法,添加分页查询条件,包装查询结果

1、创建一个插件

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可

/*
 *  指定方法签名:
 *      下面的配置表面 将会拦截在 Executor 实例中所有的 “query” 方法调用, 
 *      这里的 Executor 是负责执行低层映射语句的内部对象
 */
@Intercepts({@Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class PageInterceptor implements Interceptor {
    
    /**
     * 拦截目标对象中目标方法的执行
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //TODO 覆盖目标方法,我们在这里覆盖目标方法
        
        // 执行目标方法,并返回结果(这里没有处理)
        return invocation.proceed();
    }

    /**
     * 包装目标对象,即为目标对象创建一个代理对象
     */
    @Override
    public Object plugin(Object target) {
        // 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包装我们的目标对象
        // target: 目标对象, interceptor: 拦截器, this 表示使用当前拦截器
        return  Plugin.wrap(target, this);
    }

    /**
     * 获取插件注册时,传入的property属性
     */
    @Override
    public void setProperties(Properties properties) {
        // TODO 
    }
}

同时在 mybatis的配置文件中注册插件

<plugins> 
    <plugin interceptor="com.brave.interceptor.PageInterceptor">
    </plugin> 
</plugins>

上面的只是先创建了一个拦截器,还没有做具体的处理,现在我们先理清楚需要做的事情

  1. 拦截查询的目标方法,通过 Invocation 参数获取目标方法原来的参数信息
  2. 因为是分页查询,需要传入分页的参数条件,将通过是否传入有效的分页参数来判断是否要进行分页处理
  3. 除了要执行原本的查询语句,还要查询count总记录数,并计算出其他分页信息
  4. 将查询出来的结果和分页信息包装在一起并返回结果

2、创建接口和返回结果集

查询的时候,我们需要传入分页条件,这里我们创建一个 IPage 接口,为了规范统一

public interface IPage {

    /**
     * 当前页
     */
    Integer getPageNum();
    
    /**
     * 每页数量
     */
    Integer getPageSize();
    
    /**
     * 开始行
     */
    Integer getStartRow();
    
    /**
     * 排序条件
     */
    String getOrderBy();
}

并实现一个简单类 PageConfig.java

public class PageConfig implements IPage {
    private Integer pageNum;
    private Integer pageSize;
    private Integer startRow;
    private String orderBy;
    
    public PageConfig() {
        this(1,10);
    }
    public PageConfig(Integer pageNum,Integer pageSize) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
        this.startRow = (this.pageNum -1) * this.pageSize;
    }
    public Integer getPageNum() {
        return pageNum;
    }
    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
        this.startRow = (this.pageNum -1) * this.pageSize;
    }
    public Integer getPageSize() {
        return pageSize;
    }
    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
        this.startRow = (this.pageNum -1) * this.pageSize;
    }
    public String getOrderBy() {
        return orderBy;
    }
    public void setOrderBy(String orderBy) {
        this.orderBy = orderBy;
    }
    public Integer getStartRow() {
        return startRow;
    }
    public void setStartRow(Integer startRow) {
        this.startRow = startRow;
    }
}

同时我们需要包装一下返回结果集,mybatis 直接查询返回的结果集类型为 ArrayList,我们要在此基础上加上分页信息
Page.java

public class Page<E> extends ArrayList<E> {
    private static final long serialVersionUID = 1L;

    private int pageNum; // 页码
    private int pageSize; // 每页数量
    private long total; // 总记录数
    private int pages; // 页数

    public Page() {
        super();
    }
    public Page(int pageNum, int pageSize) {
        this(pageNum, pageSize, 0);
    }
    public Page(int pageNum, int pageSize, long total) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
        this.total = total;
        this.pages = (int)(this.pageSize == 0 ? 0
            : this.total % this.pageSize == 0 ? this.total / this.pageSize : (this.total / this.pageSize + 1));
    }
    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public int getPageSize() {
        return pageSize;
    }
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
    public Long getTotal() {
        return total;
    }
    public void setTotal(long total) {
        this.total = total;
        this.pages = (int)(this.pageSize == 0 ? 0
            : this.total % this.pageSize == 0 ? this.total / this.pageSize : (this.total / this.pageSize + 1));
    }
    public int getPages() {
        return pages;
    }
    public void setPages(int pages) {
        this.pages = pages;
    }
    
    @Override
    public String toString() {
        return "Page [pageNum=" + pageNum + ", pageSize=" + pageSize + ", total=" + total + ", pages=" + pages + "]";
    }
}

3、处理查询方法

我们再顺一下拦截查询方法之后的流程

  1. 通过 Invocation 参数获取目标方法原来的参数信息
  2. 分析参数是否要进行分页处理,不需要不处理,执行原来的方法
  3. 添加分页条件,并查询结果(包括查询总数)
  4. 包装结果集并返回

这里直接附上实现代码

/*
 * 指定方法签名: 下面的配置表面 将会拦截在 Executor 实例中的 “query” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts({ @Signature(
    type = Executor.class, 
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
    private final static String COUNT_SUFFIX = "_COUNT"; // count查询语句Id后缀

    /**
     * 拦截目标对象中目标方法的执行
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获得目标对象
        Executor executor = (Executor)invocation.getTarget();
        // 获得目标方法的参数
        Object[] args = invocation.getArgs();
        // MappedStatement表示的是XML中的一个SQL
        MappedStatement mappedStatement = (MappedStatement)args[0];
        Object parameter = args[1];
        System.out.println(parameter.getClass());
        RowBounds rowBounds = (RowBounds)args[2];
        ResultHandler resultHandler = (ResultHandler)args[3];

        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);

        //获取分页参数
        IPage pageConfig = getPageConfig(parameter);
        if (pageConfig != null) {
            return pageQuery(executor, mappedStatement, parameter, resultHandler, boundSql, cacheKey,
                pageConfig);
        } else {
            // 不用分页,执行目标方法
            return invocation.proceed();
        }

    }

    /**
     * 分页查询
     * 
     * @param executor
     * @param ms
     * @param parameter
     * @param resultHandler
     * @param boundSql
     * @param cacheKey
     * @param pageConfig
     * @return
     * @throws SQLException
     */
    private Object pageQuery(Executor executor, MappedStatement ms, Object parameter,
        ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey, IPage pageConfig) throws SQLException {
        Page page = new Page(pageConfig.getPageNum(), pageConfig.getPageSize());

        // 查询总数
        Long count = countQuery(executor, ms, parameter, resultHandler, boundSql);
        // 总数大于 0 ,开始分页查询
        if (count != null && count > 0) {
            page.setTotal(count);
            // 生成分页的缓存 key
            CacheKey pageKey = cacheKey;
            // 获取分页 sql
            StringBuilder pageSql = new StringBuilder(boundSql.getSql());
            if (pageConfig.getStartRow() == 0) {
                pageSql.append(" LIMIT ").append(pageConfig.getPageSize());
            } else {
                pageSql.append(" LIMIT ").append(pageConfig.getStartRow()).append(",").append(pageConfig.getPageSize());
            }
            BoundSql pageBoundSql =
                new BoundSql(ms.getConfiguration(), pageSql.toString(), boundSql.getParameterMappings(), parameter);

            List list = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
            page.addAll(list);
        }
        return page;
    }

    /**
     * count 查询,获取总记录数
     * 
     * @param executor
     * @param mappedStatement
     * @param parameter
     * @param resultHandler
     * @param boundSql
     * @return
     * @throws SQLException
     */
    private Long countQuery(Executor executor, MappedStatement mappedStatement, Object parameter,
        ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // 创建新的 MappedStatement 用于count查询
        MappedStatement countMs = newCountMappedStatement(mappedStatement);
        // 创建 count 查询的缓存 key
        CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
        // 创建 countBoundSql
        String countSql = "SELECT COUNT(*) FROM (" + boundSql.getSql() + ") T";
        BoundSql countBoundSql =
            new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
        // 执行 count 查询
        Object countResult =
            executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
        Long count = (Long)((List)countResult).get(0);
        return count;
    }

    /**
     * 新建用于count查询的MappedStatement
     * 
     * @param ms
     * @return
     */
    private MappedStatement newCountMappedStatement(MappedStatement ms) {
        String countMsId = ms.getId() + COUNT_SUFFIX;
        MappedStatement.Builder builder =
            new MappedStatement.Builder(ms.getConfiguration(), countMsId, ms.getSqlSource(), ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());

        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        // 设置返回结果为 Long
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        ResultMap resultMap =
            new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, new ArrayList<ResultMapping>(0))
                .build();
        resultMaps.add(resultMap);
        builder.resultMaps(resultMaps);

        return builder.build();
    }

    /**
     * 获取分页信息
     * 
     * @param parameter
     * @param rowBounds
     * @return
     */
    private IPage getPageConfig(Object parameter) {
        IPage pageConfig = null;
        if (parameter instanceof IPage) {
            pageConfig = (IPage)parameter;
        }else if(parameter instanceof MapperMethod.ParamMap) {
            MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap)parameter;
            for(Object value:paramMap.values()) {
                if (value instanceof IPage) {
                    pageConfig = (IPage)value;
                    break;
                }
            }
        }
        return pageConfig;
    }

    /**
     * 包装目标对象,即为目标对象创建一个代理对象
     */
    @Override
    public Object plugin(Object target) {
        // 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包装我们的目标对象
        // target: 目标对象, interceptor: 拦截器, this 表示使用当前拦截器
        return Plugin.wrap(target, this);
    }

    /**
     * 获取插件注册时,传入的property属性
     */
    @Override
    public void setProperties(Properties properties) {
        
    }
}

4、测试结果

要使用插件,记得在 mybatis xml 中配置plugin

<plugins>
    <plugin interceptor="com.brave.interceptor.PageInterceptor">
    </plugin> 
</plugins>

我这个例子适用于使用 mapper 接口,只要接口方法有 IPage 的分页查询参数,就会分页查询

public interface UpmsUserMapper {
    List<UpmsUser> selectUser(UpmsUser upmsUser, PageConfig pageConfig);

    List<UpmsUser> selectUser(IPage pageConfig);
}

我们创建测试类测试一下结果

@RunWith(SpringJUnit4ClassRunner.class)  //使用junit4进行测试  
@ContextConfiguration ("/conf/spring/applicationContext*.xml") 
public class UpmsUserMapperTest {

    @Autowired
    private UpmsUserMapper upmsUserMapper;
    
    @Test
    public void testSelectUserUpmsUserPageConfig() {
        PageConfig pageConfig = new PageConfig(1, 2);
        List<UpmsUser> list = upmsUserMapper.selectUser(new UpmsUser(), pageConfig);
        System.out.println(list.getClass());
        System.out.println(list.toString());
        for (UpmsUser upmsUser : list) {
            System.out.println(upmsUser.toString());
        }
    }
}

测试结果如下

class com.brave.page.Page
Page [pageNum=1, pageSize=2, total=3, pages=2]
[userId=10001][loginname=zou][password=123456][locked=null]
[userId=10002][loginname=zou][password=123456][locked=null]

二、分页插件 PageHelper 使用

上面的例子,只是简陋的实现一个分页插件,明白大概的流程。下面来介绍分页插件 PageHelper 的使用

PageHelper 的 github 地址 https://github.com/pagehelper/Mybatis-PageHelper

1、添加依赖

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

由于使用了 sql 解析工具,还会引入 jsqlparser.jar 包,如果不是使用 maven,还需要自己导入 pagehelper 依赖版本一致的 jsqlparser.jar 包

2、配置拦截器插件

在 mybatis 配置文件中添加拦截器插件配置

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 具体参数后面介绍 -->
        <property name="helperDialect" value="mysql"/>
    </plugin>
</plugins>

如果是 spring 项目,也可以在 spring 配置文件的 SqlSessionFactoryBean 里面添加插件配置

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <!-- 配置映射器文件 -->
    <property name="mapperLocations" value="classpath*:com/brave/dao/mapper/*Mapper.xml" />

    <!-- 插件 -->
    <property name="plugins">
        <array>
            <bean class="com.github.pagehelper.PageInterceptor">
                <property name="properties">
                    <!-- 具体参数后面介绍 -->
                    <value>
                       helperDialect=mysql
                    </value>
                </property>
            </bean>
        </array>
    </property>
</bean>

两个地方的配置选其就好,不要同时生效

下面是具体分页插件参数

参数 描述 默认值
helperDialect 分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。该属性可以指定分页插件使用哪种方言。配置值:oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
offsetAsPageNum 该参数对使用 RowBounds 作为分页参数时有效。设为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页 false
rowBoundsWithCount 该参数对使用 RowBounds 作为分页参数时有效。设置为true时,使用 RowBounds 分页会进行 count 查询 false
pageSizeZero 设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型) false
reasonable 分页合理化参数。设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询 false
params 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值,可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
supportMethodsArguments 支持通过 Mapper 接口参数来传递分页参数,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest false
autoRuntimeDialect 设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 false
closeConn 当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时,会自动获取一个数据库连接,通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为 false 后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定 true
aggregateFunctions(5.1.5+) 默认为所有常见数据库的聚合函数,允许手动添加聚合函数(影响行数),所有以聚合函数开头的函数,在进行 count 转换时,会套一层。其他函数和列会被替换为 count(0),其中count列可以自己配置。

当 offsetAsPageNum=false 的时候,由于 PageNum 问题,RowBounds查询的时候 reasonable 会强制为 false。使用 PageHelper.startPage 方法不受影响。

这些参数是在默认情况(dialect)下有效,默认情况下会使用 PageHelper 方式进行分页,如果想要实现自己的分页逻辑,可以实现 Dialect(com.github.pagehelper.Dialect) 接口,然后配置该属性为实现类的全限定名称。

3、使用方法

这里主要是4种使用方法

  • RowBounds方式的调用
  • PageHelper 的静态方法调用
  • 接口方法使用分页参数
  • ISelect 接口方式

1)RowBounds方式的调用

使用 RowBounds 参数进行分页,这种方式侵入性最小,用只是使用了这个参数,并没有增加其他任何内容

@Repository
public class UpmsUserDaoImpl extends SuperDao implements UpmsUserDao {

    @Override
    public List<UpmsUser> selectUser(int offset,int limit) {
    List<UpmsUser> list = getSqlSession().selectList("com.brave.dao.UpmsUserDao2.selectUser", null, new RowBounds(offset, limit));
    return list;
}
}

测试代码

@Test
public void testSelectUser() {
    List<UpmsUser> upmsUsers = upmsUserDao.selectUser(0, 10);
    for (UpmsUser upmsUser : upmsUsers) {
        System.out.println(upmsUser.toString());
    }
}

我们在配置插件的时候有两个参数是关于 RowBounds 的

  • offsetAsPageNum 设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页,也就是说上面的例子,我们查第一页时为 upmsUserDao.selectUser(1, 10)
  • rowBoundsWithCount RowBounds 查询默认是没有 count 查询的,设置为true时,使用 RowBounds 分页会进行 count 查询

当 rowBoundsWithCount 设置为true时,会有 count 查询,会获得查询记录总数

@Test
public void testSelectUser() {
    Page<UpmsUser> upmsUsers = (Page<UpmsUser>)upmsUserDao.selectUser(1, 10);
    System.out.println("总页数:"+upmsUsers.getTotal());
    for (UpmsUser upmsUser : upmsUsers) {
        System.out.println(upmsUser.toString());
    }
}

没有设置或为false的话,total 为 -1

如果不设置 rowBoundsWithCount,也可以通过 PageRowBounds 进行 count 查询。
getSqlSession().selectList("com.brave.dao.UpmsUserDao.selectUser", null, new PageRowBounds (offset, limit))
PageRowBounds 继承 RowBounds,多了 total 属性

另外使用接口的时候也可以增加RowBounds参数
List<Country> selectAll(RowBounds rowBounds);

2)PageHelper 的静态方法调用

PageHelper 类有 startPage 和 offsetPage 方法去设置分页参数

在需要进行分页的 mybatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个 mybatis 查询方法会被进行分页。
通常是在 service 的 bean 中调用 dao 的查询方法前 或者是 mapper 接口方法前 调用 PageHelper 的静态方法

@Test
public void testSelectUser2() {
    // request: url?pageNum=1&pageSize=10
    // 支持 ServletRequest,Map,POJO 对象,需要配合 params 参数
    // PageHelper.startPage(request);
    PageHelper.startPage(1, 10);
    Page<UpmsUser> upmsUsers = (Page<UpmsUser>)upmsUserDao.selectUser();
    System.out.println("总页数:" + upmsUsers.getTotal());
    for (UpmsUser upmsUser : upmsUsers) {
        System.out.println(upmsUser.toString());
    }
}

使用 mapper 接口的时候

@Service
public class UpmsUserServiceImpl implements UpmsUserService {
    @Autowired
    private UpmsUserMapper upmsUserMapper;

    @Override
    public List<UpmsUser> listUser(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        return upmsUserMapper.selectUser();
    }
}

使用 PageInfo
PageInfo 包含了非常全面的分页属性

List<UpmsUser> list = (Page<UpmsUser>)upmsUserDao.selectUser();
PageInfo<UpmsUser> pageInfo = new PageInfo(list);

    private int pageNum;//当前页
    private int pageSize;//每页的数量
    private int size; //当前页的数量
    
    //由于startRow和endRow不常用,这里说个具体的用法
    //可以在页面中"显示startRow到endRow 共size条数据"
    private int startRow;//当前页面第一个元素在数据库中的行号
    private int endRow;//当前页面最后一个元素在数据库中的行号
    
    private int pages;//总页数
    private int prePage;//前一页
    private int nextPage;//下一页
    private boolean isFirstPage = false;//是否为第一页
    private boolean isLastPage = false;//是否为最后一页
    private boolean hasPreviousPage = false;//是否有前一页
    private boolean hasNextPage = false;//是否有下一页
    private int navigatePages;//导航页码数
    private int[] navigatepageNums;//所有导航页号
    private int navigateFirstPage;//导航条上的第一页
    private int navigateLastPage;//导航条上的最后一页

3)使用参数的方法

想要使用参数方式,需要配置 supportMethodsArguments 参数为 true,同时要配置 params 参数

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="supportMethodsArguments" value="true"/>
        <!-- params 默认值为  pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero -->
        <property name="params" value="pageNum=pageNum;pageSize=pageSize;"/>
    </plugin>
</plugins>

可以在使用接口声明时就带上 pageNum 和 pageSize

//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<Country> selectByPageNumSize(
            @Param("upmsUser") UpmsUser upmsUser,
            @Param("pageNum") int pageNum, 
            @Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代码中直接调用:
List<Country> list = countryMapper.selectByPageNumSize(user, 1, 10);

也可以把 pageNum 和 pageSize 存在于对象中,如 UpmsUser

//存在以下 Mapper 接口方法,不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<Country> selectByPageNumSize(UpmsUser upmsUser);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<Country> list = countryMapper.selectByPageNumSize(upmsUser);

两种情况都需要 pageNum 和 pageSize 两个属性同时存在才会分页,且在 xml 中的sql中不需要处理这两个参数

4)ISelect 接口方式

另外还可以使用 ISelect 接口方式方式

Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
    @Override
    public void doSelect() {
        upmsUserMapper.selectUser();
    }
});
// lambda用法
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> upmsUserMapper.selectUser());

//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
    @Override
    public void doSelect() {
        upmsUserMapper.selectUser();
    }
});
//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> upmsUserMapper.selectUser());

//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
    @Override
    public void doSelect() {
        upmsUserMapper.selectUser(country);
    }
});
//lambda
total = PageHelper.count(()->upmsUserMapper.selectUser(country));

ISelect 接口方式除了可以保证安全外,还特别实现了将查询转换为单纯的 count 查询方式,这个方法可以将任意的查询方法,变成一个 select count(*) 的查询方法。

4、各使用方法的安全性考虑

  • 使用 RowBounds 和 PageRowBounds 参数方式是极其安全的
  • 使用参数方式是极其安全的
  • 使用 ISelect 接口调用是极其安全的

不安全的时候:

PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。

只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。

如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement 时), 这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。

下面这样的代码,就是不安全的用法:

//由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页
PageHelper.startPage(1, 10);
List<UpmsUser> list;
if(param1 != null){
    list = UpmsUserMapper.selectUser(param1);
} else {
    list = new ArrayList<UpmsUser>();
}

我们只要把 PageHelper.startPage(1, 10); 方法放到if里面查询语句之前就好了

猜你喜欢

转载自blog.csdn.net/e_zxq/article/details/86154481
今日推荐