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

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

以前为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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Mybatis分页插件PageHelper的使用详解

    1.说明 如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件. 该插件目前支持Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库分页. 2.使用方法 第一步:在Mybatis配置xml中配置拦截器插件: <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pageh

  • Mybatis全面分页插件

    根据下面分页的思想,很容易实现Mybitas的多租户设计. 使用Mybatis提供的拦截器.对分页的SQL语句通过封装处理,处理成不同的分页sql. 本例已经实现了对Mysql和Oracle的分页功能.注意下面的引用包,不要引用错了. import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.

  • Java简单实现SpringMVC+MyBatis分页插件

    1.封装分页Page类 package com.framework.common.page.impl; import java.io.Serializable; import com.framework.common.page.IPage; /** * * * */ public abstract class BasePage implements IPage, Serializable { /** * */ private static final long serialVersionUID

  • Mybatis常用分页插件实现快速分页处理技巧

    在未分享整个查询分页的执行代码之前,先了解一下执行流程. 1.总体上是利用mybatis的插件拦截器,在sql执行之前拦截,为查询语句加上limit X X 2.用一个Page对象,贯穿整个执行流程,这个Page对象需要用Java编写前端分页组件 3.用一套比较完整的三层entity,dao,service支持这个分页架构 4.这个分页用到的一些辅助类 注:分享的内容较多,这边的话我就不把需要的jar一一列举,大家使用这个分页功能的时候缺少什么就去晚上找什么jar包即可,尽可能用maven包导入

  • 自己动手写的mybatis分页插件(极其简单好用)

    刚开始项目,需要用到mybatis分页,网上看了很多插件,其实实现原理基本都大同小异,但是大部分都只给了代码,注释不全,所以参考了很多篇文章(每篇文章偷一点代码,评出来自己的,半抄袭),才自己模仿着写出了一个适合自己项目的分页插件,话不多说,直接上代码,相比大部分文章,注释算很完整了 最重要的拦截器 package com.dnkx.interceptor; import java.sql.*; import java.util.HashMap; import java.util.Propert

  • 详解Mybatis分页插件 - 示例代码

    这里说最好用,绝对不是吹的,不过有好多人都不理解为什么要用这个插件,自己手写分页sql不是挺好吗...... 所以我特地写这样一个例子来讲为什么最好用. 假设我们已经写好了Mapper的接口和xml,如下: public interface SysLoginLogMapper { /** * 根据查询条件查询登录日志 * @param logip * @param username * @param loginDate * @param exitDate * @return */ List<Sy

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

    注意:这篇博客已经和当前的分页插件完全不一样了,所以建议大家通过上面项目地址查看最新的源码和文档来了解. 以前为Mybatis分页查询发愁过,而且在网上搜过很多相关的文章,最后一个都没采用.在分页的地方完全都是手写分页SQL和count的sql,总之很麻烦. 后来有一段时间想从Mybatis内部写一个分页的实现,我对LanguageDriver写过一个实现,自动分页是没问题了,但是查询总数(count)仍然没法一次性解决,最后不了了之. 最近又要用到分页,为了方便必须地写个通用的分页类,因此又再

  • 详解MyBatis直接执行SQL查询及数据批量插入

    一.直接执行SQL查询: 1.mappers文件节选 <resultMap id="AcModelResultMap" type="com.izumi.InstanceModel"> <result column="instanceid" property="instanceID" jdbcType="VARCHAR" /> <result column="insta

  • 详解MyBatis逆向工程

    1.什么是mybatis逆向工程 在使用mybatis时需要程序员自己编写sql语句,针对单表的sql语句量是很大的,mybatis官方提供了一种根据数据库表生成mybatis执行代码的工具,这个工具就是一个逆向工程. 逆向工程:针对数据库单表-->生成代码(mapper.xml.mapper.java.pojo..) mybatis-generator-core-1.3.2.jar-逆向工程运行所需要的jar核心 包 2.配置逆向工程的配置文件 配置文件generatorConfig.xml

  • 详解MyBatis的getMapper()接口、resultMap标签、Alias别名、 尽量提取sql列、动态操作

    一.getMapper()接口 解析:getMapper()接口 IDept.class定义一个接口, 挂载一个没有实现的方法,特殊之处,借楼任何方法,必须和小配置中id属性是一致的 通过代理:生成接口的实现类名称,在MyBatis底层维护名称$$Dept_abc,selectDeptByNo() 相当于是一个强类型 Eg 第一步:在cn.happy.dao中定义一个接口 package cn.happy.dao; import java.util.List; import cn.happy.e

  • 详解MyBatis Generator自动创建代码(dao,mapping,poji)

    连接的数据库为SQL server2008,所以需要的文件为sqljdbc4.jar 使用的lib库有: 在lib库目录下新建一个src文件夹用来存放生成的文件,然后新建generatorConfig.xml 里面代码为: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis G

  • 详解Mybatis框架SQL防注入指南

    前言 SQL注入漏洞作为WEB安全的最常见的漏洞之一,在java中随着预编译与各种ORM框架的使用,注入问题也越来越少.新手代码审计者往往对Java Web应用的多个框架组合而心生畏惧,不知如何下手,希望通过Mybatis框架使用不当导致的SQL注入问题为例,能够抛砖引玉给新手一些思路. 一.Mybatis的SQL注入 Mybatis的SQL语句可以基于注解的方式写在类方法上面,更多的是以xml的方式写到xml文件.Mybatis中SQL语句需要我们自己手动编写或者用generator自动生成.

  • 详解Mybatis中的 ${} 和 #{}区别与用法

    Mybatis 的Mapper.xml语句中parameterType向SQL语句传参有两种方式:#{}和${} 我们经常使用的是#{},一般解说是因为这种方式可以防止SQL注入,简单的说#{}这种方式SQL语句是经过预编译的,它是把#{}中间的参数转义成字符串,举个例子: select * from student where student_name = #{name} 预编译后,会动态解析成一个参数标记符?: select * from student where student_name

  • 详解mybatis plus使用insert没有返回主键的处理

    项目使用springboot搭建.最初的时候是使用mybatis,后来升级到mybatis plus.按照mp的官网介绍,使用mp的insert方法,对于自增的数据库表,mp会把主键写入回实例的对应属性.但实际操作起来,却没有主键. entity 类设置如下: @TableName(value = "USERINFO") public class UserInfo { /** * 指定自增策略 */ @TableId(value = "user_id",type =

  • 详解MyBatis XML配置解析

    MyBatis核心配置文件 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environm

  • 详解Mybatis 传递参数类型为List的取值问题

    问题描述: 参数传递为List时: 当传递一个 List 实例或者数组作为参数对象传给 Mybatis.此时,Mybatis 会自动将它包装在一个 Map 中,用名称在作为键.List 实例将会以"list" 作为键,而数组实例将会以"array"作为键.所以,当我们传递的是一个List集合时,mybatis会自动把我们的list集合包装成以list为Key值的map. DAO 层: List<User> selectUserByIDs( List ID

随机推荐