详解Mybatis极其(最)简(好)单(用)的一个分页插件

作者:isea533 时间:2021-09-25 03:00:35 

注意:这篇博客已经和当前的分页插件完全不一样了,所以建议大家通过上面项目地址查看最新的源码和文档来了解。

以前为Mybatis分页查询发愁过,而且在网上搜过很多相关的文章,最后一个都没采用。在分页的地方完全都是手写分页SQL和count的sql,总之很麻烦。

后来有一段时间想从Mybatis内部写一个分页的实现,我对LanguageDriver写过一个实现,自动分页是没问题了,但是查询总数(count)仍然没法一次性解决,最后不了了之。

最近又要用到分页,为了方便必须地写个通用的分页类,因此又再次参考网上大多数的Mybatis分页代码。

实际上在很早之前,有人在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;
 }

/**
  * 只拦截这两种类型的
  * StatementHandler
  * 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。

由于这里只提供了Oracle的实现,所以我希望参考该分页插件实现的其他数据库的读者也能将相应的代码开源。

项目地址:http://xiazai.jb51.net/201612/yuanma/Mybatis_PageHelper_jb51.zip

来源:http://blog.csdn.net/isea533/article/details/23831273

标签:mybatis,分页插件
0
投稿

猜你喜欢

  • Android开发之开关按钮控件ToggleButton简单用法示例

    2022-12-30 22:05:43
  • c# 反射+自定义特性保存数据至本地

    2023-03-14 03:07:40
  • Java使用Tesseract-Ocr识别数字

    2022-12-30 05:15:44
  • Unity 点击UI与点击屏幕冲突的解决方案

    2022-02-22 15:50:49
  • 如何基于FTP4J实现FTPS连接过程解析

    2022-09-19 21:51:35
  • C# ADO.NET 离线查询的实现示例

    2023-06-12 00:52:03
  • Java字符串查找的三种方式

    2023-11-18 22:00:30
  • Android 判断SIM卡属于哪个移动运营商详解及实例

    2023-08-25 15:29:39
  • java在linux本地执行shell命令的实现方法

    2023-11-14 02:31:33
  • ijkPlayer播放器的全自动编译脚本及最终编译包

    2023-01-10 13:52:18
  • android中Bitmap用法(显示,保存,缩放,旋转)实例分析

    2022-07-03 14:18:41
  • Java导出Execl疑难点处理的实现

    2021-09-18 12:55:11
  • Java实现双色球抽奖随机算法示例

    2023-04-25 12:15:38
  • 简单说明Java的Struts框架中merge标签的使用方法

    2023-09-17 00:32:26
  • 详解Java使用Pipeline对Redis批量读写(hmset&hgetall)

    2023-11-17 15:17:24
  • 详解Android 蓝牙通信方式总结

    2021-06-19 19:34:44
  • android实现背景音乐播放功能

    2023-09-17 23:29:25
  • Android 实现微信登录详解

    2022-07-14 06:34:11
  • Flutter有状态组件StatefulWidget生命周期详解

    2023-09-25 23:56:50
  • Android入门之使用SharedPreference存取信息详解

    2023-09-09 10:25:53
  • asp之家 软件编程 m.aspxhome.com