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

川长思鸟来 2022-01-16 06:25 1191阅读 0赞

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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

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

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

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

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

1、创建一个插件

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

  1. /*
  2. * 指定方法签名:
  3. * 下面的配置表面 将会拦截在 Executor 实例中所有的 “query” 方法调用,
  4. * 这里的 Executor 是负责执行低层映射语句的内部对象
  5. */
  6. @Intercepts({@Signature(
  7. type = Executor.class,
  8. method = "query",
  9. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
  10. public class PageInterceptor implements Interceptor {
  11. /**
  12. * 拦截目标对象中目标方法的执行
  13. */
  14. @Override
  15. public Object intercept(Invocation invocation) throws Throwable {
  16. //TODO 覆盖目标方法,我们在这里覆盖目标方法
  17. // 执行目标方法,并返回结果(这里没有处理)
  18. return invocation.proceed();
  19. }
  20. /**
  21. * 包装目标对象,即为目标对象创建一个代理对象
  22. */
  23. @Override
  24. public Object plugin(Object target) {
  25. // 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包装我们的目标对象
  26. // target: 目标对象, interceptor: 拦截器, this 表示使用当前拦截器
  27. return Plugin.wrap(target, this);
  28. }
  29. /**
  30. * 获取插件注册时,传入的property属性
  31. */
  32. @Override
  33. public void setProperties(Properties properties) {
  34. // TODO
  35. }
  36. }

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

  1. <plugins>
  2. <plugin interceptor="com.brave.interceptor.PageInterceptor">
  3. </plugin>
  4. </plugins>

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

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

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

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

  1. public interface IPage {
  2. /**
  3. * 当前页
  4. */
  5. Integer getPageNum();
  6. /**
  7. * 每页数量
  8. */
  9. Integer getPageSize();
  10. /**
  11. * 开始行
  12. */
  13. Integer getStartRow();
  14. /**
  15. * 排序条件
  16. */
  17. String getOrderBy();
  18. }

并实现一个简单类 PageConfig.java

  1. public class PageConfig implements IPage {
  2. private Integer pageNum;
  3. private Integer pageSize;
  4. private Integer startRow;
  5. private String orderBy;
  6. public PageConfig() {
  7. this(1,10);
  8. }
  9. public PageConfig(Integer pageNum,Integer pageSize) {
  10. this.pageNum = pageNum;
  11. this.pageSize = pageSize;
  12. this.startRow = (this.pageNum -1) * this.pageSize;
  13. }
  14. public Integer getPageNum() {
  15. return pageNum;
  16. }
  17. public void setPageNum(Integer pageNum) {
  18. this.pageNum = pageNum;
  19. this.startRow = (this.pageNum -1) * this.pageSize;
  20. }
  21. public Integer getPageSize() {
  22. return pageSize;
  23. }
  24. public void setPageSize(Integer pageSize) {
  25. this.pageSize = pageSize;
  26. this.startRow = (this.pageNum -1) * this.pageSize;
  27. }
  28. public String getOrderBy() {
  29. return orderBy;
  30. }
  31. public void setOrderBy(String orderBy) {
  32. this.orderBy = orderBy;
  33. }
  34. public Integer getStartRow() {
  35. return startRow;
  36. }
  37. public void setStartRow(Integer startRow) {
  38. this.startRow = startRow;
  39. }
  40. }

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

  1. public class Page<E> extends ArrayList<E> {
  2. private static final long serialVersionUID = 1L;
  3. private int pageNum; // 页码
  4. private int pageSize; // 每页数量
  5. private long total; // 总记录数
  6. private int pages; // 页数
  7. public Page() {
  8. super();
  9. }
  10. public Page(int pageNum, int pageSize) {
  11. this(pageNum, pageSize, 0);
  12. }
  13. public Page(int pageNum, int pageSize, long total) {
  14. this.pageNum = pageNum;
  15. this.pageSize = pageSize;
  16. this.total = total;
  17. this.pages = (int)(this.pageSize == 0 ? 0
  18. : this.total % this.pageSize == 0 ? this.total / this.pageSize : (this.total / this.pageSize + 1));
  19. }
  20. public int getPageNum() {
  21. return pageNum;
  22. }
  23. public void setPageNum(int pageNum) {
  24. this.pageNum = pageNum;
  25. }
  26. public int getPageSize() {
  27. return pageSize;
  28. }
  29. public void setPageSize(int pageSize) {
  30. this.pageSize = pageSize;
  31. }
  32. public Long getTotal() {
  33. return total;
  34. }
  35. public void setTotal(long total) {
  36. this.total = total;
  37. this.pages = (int)(this.pageSize == 0 ? 0
  38. : this.total % this.pageSize == 0 ? this.total / this.pageSize : (this.total / this.pageSize + 1));
  39. }
  40. public int getPages() {
  41. return pages;
  42. }
  43. public void setPages(int pages) {
  44. this.pages = pages;
  45. }
  46. @Override
  47. public String toString() {
  48. return "Page [pageNum=" + pageNum + ", pageSize=" + pageSize + ", total=" + total + ", pages=" + pages + "]";
  49. }
  50. }

3、处理查询方法

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

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

这里直接附上实现代码

  1. /*
  2. * 指定方法签名: 下面的配置表面 将会拦截在 Executor 实例中的 “query” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象
  3. */
  4. @SuppressWarnings({"rawtypes", "unchecked"})
  5. @Intercepts({ @Signature(
  6. type = Executor.class,
  7. method = "query",
  8. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
  9. })
  10. public class PageInterceptor implements Interceptor {
  11. private final static String COUNT_SUFFIX = "_COUNT"; // count查询语句Id后缀
  12. /**
  13. * 拦截目标对象中目标方法的执行
  14. */
  15. @Override
  16. public Object intercept(Invocation invocation) throws Throwable {
  17. // 获得目标对象
  18. Executor executor = (Executor)invocation.getTarget();
  19. // 获得目标方法的参数
  20. Object[] args = invocation.getArgs();
  21. // MappedStatement表示的是XML中的一个SQL
  22. MappedStatement mappedStatement = (MappedStatement)args[0];
  23. Object parameter = args[1];
  24. System.out.println(parameter.getClass());
  25. RowBounds rowBounds = (RowBounds)args[2];
  26. ResultHandler resultHandler = (ResultHandler)args[3];
  27. BoundSql boundSql = mappedStatement.getBoundSql(parameter);
  28. CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
  29. //获取分页参数
  30. IPage pageConfig = getPageConfig(parameter);
  31. if (pageConfig != null) {
  32. return pageQuery(executor, mappedStatement, parameter, resultHandler, boundSql, cacheKey,
  33. pageConfig);
  34. } else {
  35. // 不用分页,执行目标方法
  36. return invocation.proceed();
  37. }
  38. }
  39. /**
  40. * 分页查询
  41. *
  42. * @param executor
  43. * @param ms
  44. * @param parameter
  45. * @param resultHandler
  46. * @param boundSql
  47. * @param cacheKey
  48. * @param pageConfig
  49. * @return
  50. * @throws SQLException
  51. */
  52. private Object pageQuery(Executor executor, MappedStatement ms, Object parameter,
  53. ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey, IPage pageConfig) throws SQLException {
  54. Page page = new Page(pageConfig.getPageNum(), pageConfig.getPageSize());
  55. // 查询总数
  56. Long count = countQuery(executor, ms, parameter, resultHandler, boundSql);
  57. // 总数大于 0 ,开始分页查询
  58. if (count != null && count > 0) {
  59. page.setTotal(count);
  60. // 生成分页的缓存 key
  61. CacheKey pageKey = cacheKey;
  62. // 获取分页 sql
  63. StringBuilder pageSql = new StringBuilder(boundSql.getSql());
  64. if (pageConfig.getStartRow() == 0) {
  65. pageSql.append(" LIMIT ").append(pageConfig.getPageSize());
  66. } else {
  67. pageSql.append(" LIMIT ").append(pageConfig.getStartRow()).append(",").append(pageConfig.getPageSize());
  68. }
  69. BoundSql pageBoundSql =
  70. new BoundSql(ms.getConfiguration(), pageSql.toString(), boundSql.getParameterMappings(), parameter);
  71. List list = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
  72. page.addAll(list);
  73. }
  74. return page;
  75. }
  76. /**
  77. * count 查询,获取总记录数
  78. *
  79. * @param executor
  80. * @param mappedStatement
  81. * @param parameter
  82. * @param resultHandler
  83. * @param boundSql
  84. * @return
  85. * @throws SQLException
  86. */
  87. private Long countQuery(Executor executor, MappedStatement mappedStatement, Object parameter,
  88. ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  89. // 创建新的 MappedStatement 用于count查询
  90. MappedStatement countMs = newCountMappedStatement(mappedStatement);
  91. // 创建 count 查询的缓存 key
  92. CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
  93. // 创建 countBoundSql
  94. String countSql = "SELECT COUNT(*) FROM (" + boundSql.getSql() + ") T";
  95. BoundSql countBoundSql =
  96. new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
  97. // 执行 count 查询
  98. Object countResult =
  99. executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
  100. Long count = (Long)((List)countResult).get(0);
  101. return count;
  102. }
  103. /**
  104. * 新建用于count查询的MappedStatement
  105. *
  106. * @param ms
  107. * @return
  108. */
  109. private MappedStatement newCountMappedStatement(MappedStatement ms) {
  110. String countMsId = ms.getId() + COUNT_SUFFIX;
  111. MappedStatement.Builder builder =
  112. new MappedStatement.Builder(ms.getConfiguration(), countMsId, ms.getSqlSource(), ms.getSqlCommandType());
  113. builder.resource(ms.getResource());
  114. builder.fetchSize(ms.getFetchSize());
  115. builder.statementType(ms.getStatementType());
  116. builder.keyGenerator(ms.getKeyGenerator());
  117. builder.resultSetType(ms.getResultSetType());
  118. builder.cache(ms.getCache());
  119. builder.flushCacheRequired(ms.isFlushCacheRequired());
  120. builder.useCache(ms.isUseCache());
  121. builder.timeout(ms.getTimeout());
  122. builder.parameterMap(ms.getParameterMap());
  123. if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
  124. StringBuilder keyProperties = new StringBuilder();
  125. for (String keyProperty : ms.getKeyProperties()) {
  126. keyProperties.append(keyProperty).append(",");
  127. }
  128. keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
  129. builder.keyProperty(keyProperties.toString());
  130. }
  131. // 设置返回结果为 Long
  132. List<ResultMap> resultMaps = new ArrayList<ResultMap>();
  133. ResultMap resultMap =
  134. new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, new ArrayList<ResultMapping>(0))
  135. .build();
  136. resultMaps.add(resultMap);
  137. builder.resultMaps(resultMaps);
  138. return builder.build();
  139. }
  140. /**
  141. * 获取分页信息
  142. *
  143. * @param parameter
  144. * @param rowBounds
  145. * @return
  146. */
  147. private IPage getPageConfig(Object parameter) {
  148. IPage pageConfig = null;
  149. if (parameter instanceof IPage) {
  150. pageConfig = (IPage)parameter;
  151. }else if(parameter instanceof MapperMethod.ParamMap) {
  152. MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap)parameter;
  153. for(Object value:paramMap.values()) {
  154. if (value instanceof IPage) {
  155. pageConfig = (IPage)value;
  156. break;
  157. }
  158. }
  159. }
  160. return pageConfig;
  161. }
  162. /**
  163. * 包装目标对象,即为目标对象创建一个代理对象
  164. */
  165. @Override
  166. public Object plugin(Object target) {
  167. // 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包装我们的目标对象
  168. // target: 目标对象, interceptor: 拦截器, this 表示使用当前拦截器
  169. return Plugin.wrap(target, this);
  170. }
  171. /**
  172. * 获取插件注册时,传入的property属性
  173. */
  174. @Override
  175. public void setProperties(Properties properties) {
  176. }
  177. }

4、测试结果

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

  1. <plugins>
  2. <plugin interceptor="com.brave.interceptor.PageInterceptor">
  3. </plugin>
  4. </plugins>

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

  1. public interface UpmsUserMapper {
  2. List<UpmsUser> selectUser(UpmsUser upmsUser, PageConfig pageConfig);
  3. List<UpmsUser> selectUser(IPage pageConfig);
  4. }

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

  1. @RunWith(SpringJUnit4ClassRunner.class) //使用junit4进行测试
  2. @ContextConfiguration ("/conf/spring/applicationContext*.xml")
  3. public class UpmsUserMapperTest {
  4. @Autowired
  5. private UpmsUserMapper upmsUserMapper;
  6. @Test
  7. public void testSelectUserUpmsUserPageConfig() {
  8. PageConfig pageConfig = new PageConfig(1, 2);
  9. List<UpmsUser> list = upmsUserMapper.selectUser(new UpmsUser(), pageConfig);
  10. System.out.println(list.getClass());
  11. System.out.println(list.toString());
  12. for (UpmsUser upmsUser : list) {
  13. System.out.println(upmsUser.toString());
  14. }
  15. }
  16. }

测试结果如下

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

二、分页插件 PageHelper 使用

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

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

1、添加依赖

  1. <dependency>
  2. <groupId>com.github.pagehelper</groupId>
  3. <artifactId>pagehelper</artifactId>
  4. <version>5.1.8</version>
  5. </dependency>

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

2、配置拦截器插件

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

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

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

  1. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  2. <property name="dataSource" ref="dataSource" />
  3. <!-- 配置映射器文件 -->
  4. <property name="mapperLocations" value="classpath*:com/brave/dao/mapper/*Mapper.xml" />
  5. <!-- 插件 -->
  6. <property name="plugins">
  7. <array>
  8. <bean class="com.github.pagehelper.PageInterceptor">
  9. <property name="properties">
  10. <!-- 具体参数后面介绍 -->
  11. <value>
  12. helperDialect=mysql
  13. </value>
  14. </property>
  15. </bean>
  16. </array>
  17. </property>
  18. </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 参数进行分页,这种方式侵入性最小,用只是使用了这个参数,并没有增加其他任何内容

  1. @Repository
  2. public class UpmsUserDaoImpl extends SuperDao implements UpmsUserDao {
  3. @Override
  4. public List<UpmsUser> selectUser(int offset,int limit) {
  5. List<UpmsUser> list = getSqlSession().selectList("com.brave.dao.UpmsUserDao2.selectUser", null, new RowBounds(offset, limit));
  6. return list;
  7. }
  8. }

测试代码

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

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

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

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

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

没有设置或为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 的静态方法

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

mapper 接口

  1. @Service
  2. public class UpmsUserServiceImpl implements UpmsUserService {
  3. @Autowired
  4. private UpmsUserMapper upmsUserMapper;
  5. @Override
  6. public List<UpmsUser> listUser(int pageNum, int pageSize) {
  7. PageHelper.startPage(pageNum, pageSize);
  8. return upmsUserMapper.selectUser();
  9. }
  10. }

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

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

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

3)使用参数的方法

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

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

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

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

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

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

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

4)ISelect 接口方式

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

  1. Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
  2. @Override
  3. public void doSelect() {
  4. upmsUserMapper.selectUser();
  5. }
  6. });
  7. // lambda用法
  8. Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> upmsUserMapper.selectUser());
  9. //也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
  10. pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
  11. @Override
  12. public void doSelect() {
  13. upmsUserMapper.selectUser();
  14. }
  15. });
  16. //对应的lambda用法
  17. pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> upmsUserMapper.selectUser());
  18. //count查询,返回一个查询语句的count数
  19. long total = PageHelper.count(new ISelect() {
  20. @Override
  21. public void doSelect() {
  22. upmsUserMapper.selectUser(country);
  23. }
  24. });
  25. //lambda
  26. 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 参数被错误的使用。

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

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

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

转载于:https://my.oschina.net/morgan412/blog/3000209

发表评论

表情:
评论列表 (有 0 条评论,1191人围观)

还没有评论,来说两句吧...

相关阅读

    相关 MyBatisPageHelper

    PageHelper是基于MyBatis的一个分页插件,能够支持各类数据库的分页需求使得分页操作能够更加便捷。 Maven依赖引入 使用pagehelper插件主要依赖

    相关 mybatisPageHelper

        最近有个小项目,不和旧系统有关系,所以简单搭建了一个SSM框架。在项目进行中,遇到需要分页显示数据的需求,记得之前接触mybatis框架用的就是插件,很方便,所以这次