最好的 Mybatis分页插件PageHelper
如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件。该插件目前支持Oracle,Mysql,Hsqldb,PostgreSQL四种数据库分页。由于文档内容太多,贴在这里比较乱,所以提供下面的链接,强烈推荐阅读【如何使用分页插件】http://git.oschina.net/free/Mybatis_PageHelper/wikis/HowToUse
使用参考 http://www.cnblogs.com/digdeep/p/4608933.html
以前使用ibatis/mybatis,都是自己手写sql语句进行物理分页,虽然稍微有点麻烦,但是都习惯了。最近试用了下mybatis的分页插件 PageHelper,感觉还不错吧。记录下其使用方法。
1. 引入依赖jar包:
1 2 3 4 5 6 |
Xml代码 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> |
2. 配置分页拦截器
PageHelper的原理是基于拦截器实现的。拦截器的配置有两种方法,一种是在mybatis的配置文件中配置,一种是直接在spring的配置文件中进行:
1)在mybatis-config.xml文件中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
Xml代码 <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> <!-- 该参数默认为false --> <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 --> <!-- 和startPage中的pageNum效果一样--> <property name="offsetAsPageNum" value="true"/> <!-- 该参数默认为false --> <!-- 设置为true时,使用RowBounds分页会进行count查询 --> <property name="rowBoundsWithCount" value="true"/> <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 --> <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) <property name="pageSizeZero" value="true"/>--> <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 --> <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 --> <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 --> <property name="reasonable" value="true"/> <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 --> <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 --> <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 --> <!-- 不理解该含义的前提下,不要随便复制该配置 <property name="params" value="pageNum=start;pageSize=limit;"/> --> </plugin> </plugins> |
这里要注意 <plugins> 在mybatis-config.xml文件中的位置,必须要符合 http://mybatis.org/dtd/mybatis-3-config.dtd 中指定的顺序:
1 2 3 |
Xml代码 <!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?)> |
不然会报错。
当然mybatis-config.xml的位置,我们需要在spring的配置文件中进行指定:
1 2 3 4 5 6 |
Xml代码 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:config/mybatis-config.xml" /> <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" /> </bean> |
2)如果mybatis没有mybatis-config.xml文件,那么就只能直接在spring的配置文件中配置了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Xml代码 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations"> <array> <value>classpath:config/mapper/*.xml</value> </array> </property> <property name="typeAliasesPackage" value="com.test.pojo"/> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <value> dialect=mysql </value> </property> </bean> </array> </property> </bean> |
到这里PageHelper所需要的配置已经完成,下面还需要在serviceImpl类中加入分页参数的代码:
3. 向拦截器传递分页参数
我们首先看下不分页的serviceImpl代码:
1 2 3 4 5 6 7 8 |
Java代码 @Override public List<User> getUserByNoAndEmail(String no, String email) { Map<String, Object> map = new HashMap<>(); map.put("no", no); map.put("email", email); return this.userMapper.getUserByNoAndEmail(map); } |
然后我们将它改造成使用PageHelper分页:
1)首先我们根据自己项目的情况,定义一个PageBean,来保存分页之后的结果,需要哪些属性,就加入哪些属性,具体可以参考源代码中的PageInfo类的定义,其实PageInfo是插件作者给我们自己定义自己的PageBean,提供的一个参考例子。PageInfo代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
Java代码 @SuppressWarnings({"rawtypes", "unchecked"}) public class PageInfo<T> implements Serializable { private static final long serialVersionUID = 1L; //当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 private int size; //由于startRow和endRow不常用,这里说个具体的用法 //可以在页面中"显示startRow到endRow 共size条数据" //当前页面第一个元素在数据库中的行号 private int startRow; //当前页面最后一个元素在数据库中的行号 private int endRow; //总记录数 private long total; //总页数 private int pages; //结果集 private List<T> list; //第一页 private int firstPage; //前一页 private int prePage; //下一页 private int nextPage; //最后一页 private int lastPage; //是否为第一页 private boolean isFirstPage = false; //是否为最后一页 private boolean isLastPage = false; //是否有前一页 private boolean hasPreviousPage = false; //是否有下一页 private boolean hasNextPage = false; //导航页码数 private int navigatePages; //所有导航页号 private int[] navigatepageNums; /** * 包装Page对象 * * @param list */ public PageInfo(List<T> list) { this(list, 8); } /** * 包装Page对象 * * @param list page结果 * @param navigatePages 页码数量 */ public PageInfo(List<T> list, int navigatePages) { if (list instanceof Page) { Page page = (Page) list; this.pageNum = page.getPageNum(); this.pageSize = page.getPageSize(); this.total = page.getTotal(); this.pages = page.getPages(); this.list = page; this.size = page.size(); //由于结果是>startRow的,所以实际的需要+1 if (this.size == 0) { this.startRow = 0; this.endRow = 0; } else { this.startRow = page.getStartRow() + 1; //计算实际的endRow(最后一页的时候特殊) this.endRow = this.startRow - 1 + this.size; } this.navigatePages = navigatePages; //计算导航页 calcNavigatepageNums(); //计算前后页,第一页,最后一页 calcPage(); //判断页面边界 judgePageBoudary(); } } /** * 计算导航页 */ private void calcNavigatepageNums() { //当总页数小于或等于导航页码数时 if (pages <= navigatePages) { navigatepageNums = new int[pages]; for (int i = 0; i < pages; i++) { navigatepageNums[i] = i + 1; } } else { //当总页数大于导航页码数时 navigatepageNums = new int[navigatePages]; int startNum = pageNum - navigatePages / 2; int endNum = pageNum + navigatePages / 2; if (startNum < 1) { startNum = 1; //(最前navigatePages页 for (int i = 0; i < navigatePages; i++) { navigatepageNums[i] = startNum++; } } else if (endNum > pages) { endNum = pages; //最后navigatePages页 for (int i = navigatePages - 1; i >= 0; i--) { navigatepageNums[i] = endNum--; } } else { //所有中间页 for (int i = 0; i < navigatePages; i++) { navigatepageNums[i] = startNum++; } } } } /** * 计算前后页,第一页,最后一页 */ private void calcPage() { if (navigatepageNums != null && navigatepageNums.length > 0) { firstPage = navigatepageNums[0]; lastPage = navigatepageNums[navigatepageNums.length - 1]; if (pageNum > 1) { prePage = pageNum - 1; } if (pageNum < pages) { nextPage = pageNum + 1; } } } /** * 判定页面边界 */ private void judgePageBoudary() { isFirstPage = pageNum == 1; isLastPage = pageNum == pages; hasPreviousPage = pageNum > 1; hasNextPage = pageNum < pages; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getPageNum() { return pageNum; } public int getPageSize() { return pageSize; } public int getSize() { return size; } public int getStartRow() { return startRow; } public int getEndRow() { return endRow; } public long getTotal() { return total; } public int getPages() { return pages; } public List<T> getList() { return list; } public int getFirstPage() { return firstPage; } public int getPrePage() { return prePage; } public int getNextPage() { return nextPage; } public int getLastPage() { return lastPage; } public boolean isIsFirstPage() { return isFirstPage; } public boolean isIsLastPage() { return isLastPage; } public boolean isHasPreviousPage() { return hasPreviousPage; } public boolean isHasNextPage() { return hasNextPage; } public int getNavigatePages() { return navigatePages; } public int[] getNavigatepageNums() { return navigatepageNums; } @Override public String toString() { final StringBuffer sb = new StringBuffer("PageInfo{"); sb.append("pageNum=").append(pageNum); sb.append(", pageSize=").append(pageSize); sb.append(", size=").append(size); sb.append(", startRow=").append(startRow); sb.append(", endRow=").append(endRow); sb.append(", total=").append(total); sb.append(", pages=").append(pages); sb.append(", list=").append(list); sb.append(", firstPage=").append(firstPage); sb.append(", prePage=").append(prePage); sb.append(", nextPage=").append(nextPage); sb.append(", lastPage=").append(lastPage); sb.append(", isFirstPage=").append(isFirstPage); sb.append(", isLastPage=").append(isLastPage); sb.append(", hasPreviousPage=").append(hasPreviousPage); sb.append(", hasNextPage=").append(hasNextPage); sb.append(", navigatePages=").append(navigatePages); sb.append(", navigatepageNums="); if (navigatepageNums == null) sb.append("null"); else { sb.append('['); for (int i = 0; i < navigatepageNums.length; ++i) sb.append(i == 0 ? "" : ", ").append(navigatepageNums[i]); sb.append(']'); } sb.append('}'); return sb.toString(); } } |
因为PageInfo.java只是一个示例,所以他定义得有点重量级,属性有点多,我们可以参考它,定义适合我们自己的PageBean, 比如如下定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
Java代码 public class PageBean<T> implements Serializable { private static final long serialVersionUID = 8656597559014685635L; private long total; //总记录数 private List<T> list; //结果集 private int pageNum; // 第几页 private int pageSize; // 每页记录数 private int pages; // 总页数 private int size; // 当前页的数量 <= pageSize,该属性来自ArrayList的size属性 /** * 包装Page对象,因为直接返回Page对象,在JSON处理以及其他情况下会被当成List来处理, * 而出现一些问题。 * @param list page结果 * @param navigatePages 页码数量 */ public PageBean(List<T> list) { if (list instanceof Page) { Page<T> page = (Page<T>) list; this.pageNum = page.getPageNum(); this.pageSize = page.getPageSize(); this.total = page.getTotal(); this.pages = page.getPages(); this.list = page; this.size = page.size(); } } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public List<T> getList() { return list; } public void setList(List<T> list) { this.list = list; } 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 int getPages() { return pages; } public void setPages(int pages) { this.pages = pages; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } } |
因为分页查询结果返回的是一个 Page 对象,而 Page 对象继承自ArrayList,但是如果我们直接返回ArrayList的话,在一些场景下回遇到问题,比如在JSON处理Page类型的结果时,会被当成List来JSON格式化,会丢弃 Page 对象的所有扩展属性,所以这里我们要将分页的结果 Page 类型转换成我们自己定义的 PageBean. 我们自己定义的PageBean没有继承ArrayList,而是包含一个List属性来保存分页结果。所以避免前面的问题。
2)修改 serviceImpl中的代码:
1 2 3 4 5 6 7 8 9 10 11 |
Java代码 @Override public PageBean<User> getUserByNoAndEmail(String no, String email) { Map<String, Object> map = new HashMap<>(); map.put("no", no); map.put("email", email); PageHelper.startPage(PaginationContext.getPageNum(), PaginationContext.getPageSize()); List<User> list = this.userMapper.getUserByNoAndEmail(map); return new PageBean<User>(list); } |
我们只需要使用 PageHelper.startPage(pageNum, pageSize); 函数来指定 pageNum(第几页) 和 pageSize(每页显示几条记录) 两个参数。然后调用原来的查询,就进行了分页。最后将返回的List,转换成 PageBean类型的结果即可。前台页面就可以根据PageBean中包括的属性来进行分页显示了。
上面的 PaginationContext 是基于 ThreadLocal 来传递分页参数的一个工具类,其实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
Java代码 public class PaginationContext { // 定义两个threadLocal变量:pageNum和pageSize private static ThreadLocal<Integer> pageNum = new ThreadLocal<Integer>(); // 保存第几页 private static ThreadLocal<Integer> pageSize = new ThreadLocal<Integer>(); // 保存每页记录条数 /* * pageNum :get、set、remove */ public static int getPageNum() { Integer pn = pageNum.get(); if (pn == null) { return 0; } return pn; } public static void setPageNum(int pageNumValue) { pageNum.set(pageNumValue); } public static void removePageNum() { pageNum.remove(); } /* * pageSize :get、set、remove */ public static int getPageSize() { Integer ps = pageSize.get(); if (ps == null) { return 0; } return ps; } public static void setPageSize(int pageSizeValue) { pageSize.set(pageSizeValue); } public static void removePageSize() { pageSize.remove(); } } |
实现了前台页面向ServiceImpl中传递分页参数: pageNum 和 pageSize.
当然从请求中获取分页参数pageNum和pageSize需要用到filter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
Java代码 public class PageFilter implements Filter { public PageFilter() {} public void destroy() {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; PaginationContext.setPageNum(getPageNum(httpRequest)); PaginationContext.setPageSize(getPageSize(httpRequest)); try { chain.doFilter(request, response); } // 使用完Threadlocal,将其删除。使用finally确保一定将其删除 finally { PaginationContext.removePageNum(); PaginationContext.removePageSize(); } } /** * 获得pager.offset参数的值 * * @param request * @return */ protected int getPageNum(HttpServletRequest request) { int pageNum = 1; try { String pageNums = request.getParameter("pageNum"); if (pageNums != null && StringUtils.isNumeric(pageNums)) { pageNum = Integer.parseInt(pageNums); } } catch (NumberFormatException e) { e.printStackTrace(); } return pageNum; } /** * 设置默认每页大小 * * @return */ protected int getPageSize(HttpServletRequest request) { int pageSize = 10; // 默认每页10条记录 try { String pageSizes = request.getParameter("pageSize"); if (pageSizes != null && StringUtils.isNumeric(pageSizes)) { pageSize = Integer.parseInt(pageSizes); } } catch (NumberFormatException e) { e.printStackTrace(); } return pageSize; } /** * @see Filter#init(FilterConfig) */ public void init(FilterConfig fConfig) throws ServletException {} } |
PageFilter在web.xml中的配置:
1 2 3 4 5 6 7 8 9 10 |
Xml代码 <!-- pagination filter --> <filter> <filter-name>PageFilter</filter-name> <filter-class>com.ems.filter.PageFilter</filter-class> </filter> <filter-mapping> <filter-name>PageFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
OK,到此,PageHelper的使用方法,基本结束。
PageHelper 项目地址:http://git.oschina.net/free/Mybatis_PageHelper
文档地址:http://git.oschina.net/free/Mybatis_PageHelper/blob/master/wikis/HowToUse.markdown