[转]Mybatis极其(最)简(好)单(用)的一个分页插件
原文地址:http://blog.csdn.net/isea533/article/details/23831273
分页插件示例:http://blog.csdn.net/isea533/article/details/24700339
最新版分页插件:http://blog.csdn.net/isea533/article/details/25505413
项目地址:http://git.oschina.net/free/Mybatis_PageHelper
以前为Mybatis分页查询发愁过,而且在网上搜过很多相关的文章,最后一个都没采用。在分页的地方完全都是手写分页SQL和count的sql,总之很麻烦。
后来有一段时间想从Mybatis内部写一个分页的实现,我对LanguageDriver写过一个实现,自动分页是没问题了,但是查询总数(count)仍然没法一次性解决,最后不了了之。
最近又要用到分页,为了方便必须地写个通用的分页类,因此又再次参考网上大多数的Mybatis分页代码,本插件主要参考自:
http://blog.csdn.net/hupanfeng/article/details/9265341
实际上在很早之前,有人在github上开源过一个实现,支持mysql,oracle,sqlserver的,和上面这个参考的比较类似,考虑的 更全面。但是我觉得太多类太麻烦了,所以自己实现了一个只有一个拦截器的类,实际上可以分为两个类,其中一个类被我写成静态类放在了拦截器中,你也可以将 Page类提取出来,方便使用Page。
先说实现方法,该插件只有一个类:PageHelper.java
拦截器签名为:
- @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
- @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
这里的签名对整个实现和思想至关重要,首先我拦截prepare方法来改分页SQL,来做count查询。然后我拦截handleResultSets方法来获取最后的处理结果,将结果放到Page对象中。
下面是修改分页的代码,是针对Oracle数据进行的修改,如果有用其他数据库的,自己修改这里的代码就可以。
- /**
- * 修改原SQL为分页SQL
- * @param sql
- * @param page
- * @return
- */
- private String buildPageSql(String sql, Page page) {
- StringBuilder pageSql = new StringBuilder(200);
- pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
- pageSql.append(sql);
- pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
- pageSql.append(") where row_id > ").append(page.getStartRow());
- return pageSql.toString();
- }
之后在下面的setPageParameter方法中一个selelct count语句,这里也需要根据数据库类型进行修改:
- // 记录总记录数
- String countSql = "select count(0) from (" + sql + ")";
为什么我不提供对各种数据库的支持呢,我觉得没必要,还有些数据库不支持分页,而且这个插件越简单对使用的开发人员来说越容易理解,越容易修改。修改成自己需要的分页查询肯定不是问题。
最后上完整代码(继续看下去,下面还有使用方法):(点击下载)
- package com.mybatis.util;
- import org.apache.ibatis.executor.parameter.ParameterHandler;
- import org.apache.ibatis.executor.resultset.ResultSetHandler;
- 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.MetaObject;
- import org.apache.ibatis.reflection.SystemMetaObject;
- import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
- import org.apache.log4j.Logger;
- import java.sql.*;
- import java.util.List;
- import java.util.Properties;
- /**
- * Mybatis - 通用分页拦截器
- * @author liuzh/abel533/isea
- * Created by liuzh on 14-4-15.
- */
- @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
- @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
- public class PageHelper implements Interceptor {
- private static final Logger logger = Logger.getLogger(PageHelper.class);
- public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();
- /**
- * 开始分页
- * @param pageNum
- * @param pageSize
- */
- public static void startPage(int pageNum, int pageSize) {
- localPage.set(new Page(pageNum, pageSize));
- }
- /**
- * 结束分页并返回结果,该方法必须被调用,否则localPage会一直保存下去,直到下一次startPage
- * @return
- */
- public static Page endPage() {
- Page page = localPage.get();
- localPage.remove();
- return page;
- }
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- if (localPage.get() == null) {
- return invocation.proceed();
- }
- if (invocation.getTarget() instanceof StatementHandler) {
- StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
- MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
- // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环
- // 可以分离出最原始的的目标类)
- while (metaStatementHandler.hasGetter("h")) {
- Object object = metaStatementHandler.getValue("h");
- metaStatementHandler = SystemMetaObject.forObject(object);
- }
- // 分离最后一个代理对象的目标类
- while (metaStatementHandler.hasGetter("target")) {
- Object object = metaStatementHandler.getValue("target");
- metaStatementHandler = SystemMetaObject.forObject(object);
- }
- MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
- //分页信息if (localPage.get() != null) {
- Page page = localPage.get();
- BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
- // 分页参数作为参数对象parameterObject的一个属性
- String sql = boundSql.getSql();
- // 重写sql
- String pageSql = buildPageSql(sql, page);
- //重写分页sql
- metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
- Connection connection = (Connection) invocation.getArgs()[0];
- // 重设分页参数里的总页数等
- setPageParameter(sql, connection, mappedStatement, boundSql, page);
- // 将执行权交给下一个拦截器
- return invocation.proceed();
- } else if (invocation.getTarget() instanceof ResultSetHandler) {
- Object result = invocation.proceed();
- Page page = localPage.get();
- page.setResult((List) result);
- return result;
- }
- return null;
- }
- /**
- * 只拦截这两种类型的
- * <br>StatementHandler
- * <br>ResultSetHandler
- * @param target
- * @return
- */
- @Override
- public Object plugin(Object target) {
- if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
- return Plugin.wrap(target, this);
- } else {
- return target;
- }
- }
- @Override
- public void setProperties(Properties properties) {
- }
- /**
- * 修改原SQL为分页SQL
- * @param sql
- * @param page
- * @return
- */
- private String buildPageSql(String sql, Page page) {
- StringBuilder pageSql = new StringBuilder(200);
- pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
- pageSql.append(sql);
- pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
- pageSql.append(") where row_id > ").append(page.getStartRow());
- return pageSql.toString();
- }
- /**
- * 获取总记录数
- * @param sql
- * @param connection
- * @param mappedStatement
- * @param boundSql
- * @param page
- */
- private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
- BoundSql boundSql, Page page) {
- // 记录总记录数
- String countSql = "select count(0) from (" + sql + ")";
- PreparedStatement countStmt = null;
- ResultSet rs = null;
- 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();
- int totalCount = 0;
- if (rs.next()) {
- totalCount = rs.getInt(1);
- }
- page.setTotal(totalCount);
- int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
- page.setPages(totalPage);
- } catch (SQLException e) {
- logger.error("Ignore this exception", e);
- } finally {
- try {
- rs.close();
- } catch (SQLException e) {
- logger.error("Ignore this exception", e);
- }
- try {
- countStmt.close();
- } catch (SQLException e) {
- logger.error("Ignore this exception", e);
- }
- }
- }
- /**
- * 代入参数值
- * @param ps
- * @param mappedStatement
- * @param boundSql
- * @param parameterObject
- * @throws SQLException
- */
- private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
- Object parameterObject) throws SQLException {
- ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
- parameterHandler.setParameters(ps);
- }
- /**
- * Description: 分页
- * Author: liuzh
- * Update: liuzh(2014-04-16 10:56)
- */
- public static class Page<E> {
- private int pageNum;
- private int pageSize;
- private int startRow;
- private int endRow;
- private long total;
- private int pages;
- private List<E> result;
- public Page(int pageNum, int pageSize) {
- this.pageNum = pageNum;
- this.pageSize = pageSize;
- this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;
- this.endRow = pageNum * pageSize;
- }
- public List<E> getResult() {
- return result;
- }
- public void setResult(List<E> result) {
- this.result = result;
- }
- public int getPages() {
- return pages;
- }
- public void setPages(int pages) {
- this.pages = pages;
- }
- public int getEndRow() {
- return endRow;
- }
- public void setEndRow(int endRow) {
- this.endRow = endRow;
- }
- 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 getStartRow() {
- return startRow;
- }
- public void setStartRow(int startRow) {
- this.startRow = startRow;
- }
- public long getTotal() {
- return total;
- }
- public void setTotal(long total) {
- this.total = total;
- }
- @Override
- public String toString() {
- return "Page{" +
- "pageNum=" + pageNum +
- ", pageSize=" + pageSize +
- ", startRow=" + startRow +
- ", endRow=" + endRow +
- ", total=" + total +
- ", pages=" + pages +
- '}';
- }
- }
- }
使用该拦截器首先需要在Mybatis配置中配置该拦截器:
- <plugins>
- <plugin interceptor="com.mybatis.util.PageHelper"></plugin>
- </plugins>
配置拦截器的时候需要注意plugins的位置,plugins位置顺序如下:
- properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?
最后是调用该方法的例子代码(Service层):
- @Override
- public PageHelper.Page<SysLoginLog> findSysLoginLog(String loginIp,
- String username,
- String loginDate,
- String exitDate,
- String logerr,
- int pageNumber,
- int pageSize) throws BusinessException {
- PageHelper.startPage(pageNumber,pageSize);
- sysLoginLogMapper.findSysLoginLog(loginIp, username, loginDate, exitDate, logerr);
- return PageHelper.endPage();
- }
从上面可以看到使用该插件使用起来是很简单的,只需要在查询前后使用PageHelper的startPage和endPage方法即可,中间代码 的调用结果已经存在于Page的result中,如果你在一个返回一个结果的地方调用PageHelper,返回的结果仍然是一个List,取第一个值即 可(我想没人会在这种地方这么用,当然这样也不出错)。
另外在startPage和endPage中间的所有mybatis代码都会被分页,而且PageHelper只会保留最后一次的结果,因而使用时需要保证每次只在其中执行一个mybatis查询,如果有多个分页,请多次使用startPage和endPage。