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.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;

import com.yidao.utils.Page;
import com.yidao.utils.ReflectHelper;

/**
 *
 * 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。
 * 利用拦截器实现Mybatis分页的原理:
 * 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句
 * 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的
 * prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用
 * StatementHandler对象的prepare方法,即调用invocation.proceed()。
 * 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设
 * 置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。
 *
 */
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
public class PageInterceptor implements Interceptor {
 private String dialect = ""; //数据库方言
  private String pageSqlId = ""; //mapper.xml中需要拦截的ID(正则匹配) 

  public Object intercept(Invocation invocation) throws Throwable {
   //对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler,
    //BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
    //SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是
    //处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个
    //StatementHandler类型的delegate属性,RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、
    //PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。
    //我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,又因为Mybatis只有在建立RoutingStatementHandler的时候
    //是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
    if(invocation.getTarget() instanceof RoutingStatementHandler){
      RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget();
      StatementHandler delegate = (StatementHandler) ReflectHelper.getFieldValue(statementHandler, "delegate");
      BoundSql boundSql = delegate.getBoundSql();
      Object obj = boundSql.getParameterObject();
      if (obj instanceof Page<?>) {
        Page<?> page = (Page<?>) obj;
        //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
        MappedStatement mappedStatement = (MappedStatement)ReflectHelper.getFieldValue(delegate, "mappedStatement");
        //拦截到的prepare方法参数是一个Connection对象
        Connection connection = (Connection)invocation.getArgs()[0];
        //获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
        String sql = boundSql.getSql();
        //给当前的page参数对象设置总记录数
        this.setTotalRecord(page,
            mappedStatement, connection);
        //获取分页Sql语句
        String pageSql = this.getPageSql(page, sql);
        //利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
        ReflectHelper.setFieldValue(boundSql, "sql", pageSql);
      }
    }
    return invocation.proceed();
  }

  /**
   * 给当前的参数对象page设置总记录数
   *
   * @param page Mapper映射语句对应的参数对象
   * @param mappedStatement Mapper映射语句
   * @param connection 当前的数据库连接
   */
  private void setTotalRecord(Page<?> page,
      MappedStatement mappedStatement, Connection connection) {
    //获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
    //delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
    BoundSql boundSql = mappedStatement.getBoundSql(page);
    //获取到我们自己写在Mapper映射语句中对应的Sql语句
    String sql = boundSql.getSql();
    //通过查询Sql语句获取到对应的计算总记录数的sql语句
    String countSql = this.getCountSql(sql);
    //通过BoundSql获取对应的参数映射
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    //利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
    BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
    //通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
    ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
    //通过connection建立一个countSql对应的PreparedStatement对象。
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = connection.prepareStatement(countSql);
      //通过parameterHandler给PreparedStatement对象设置参数
      parameterHandler.setParameters(pstmt);
      //之后就是执行获取总记录数的Sql语句和获取结果了。
      rs = pstmt.executeQuery();
      if (rs.next()) {
       int totalRecord = rs.getInt(1);
       //给当前的参数page对象设置总记录数
       page.setTotalRecord(totalRecord);
      }
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
       if (rs != null)
         rs.close();
        if (pstmt != null)
         pstmt.close();
      } catch (SQLException e) {
       e.printStackTrace();
      }
    }
  } 

  /**
   * 根据原Sql语句获取对应的查询总记录数的Sql语句
   * @param sql
   * @return
   */
  private String getCountSql(String sql) {
    int index = sql.indexOf("from");
    return "select count(*) " + sql.substring(index);
  } 

  /**
   * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle
   * 其它的数据库都 没有进行分页
   *
   * @param page 分页对象
   * @param sql 原sql语句
   * @return
   */
  private String getPageSql(Page<?> page, String sql) {
    StringBuffer sqlBuffer = new StringBuffer(sql);
    if ("mysql".equalsIgnoreCase(dialect)) {
      return getMysqlPageSql(page, sqlBuffer);
    } else if ("oracle".equalsIgnoreCase(dialect)) {
      return getOraclePageSql(page, sqlBuffer);
    }
    return sqlBuffer.toString();
  } 

  /**
  * 获取Mysql数据库的分页查询语句
  * @param page 分页对象
  * @param sqlBuffer 包含原sql语句的StringBuffer对象
  * @return Mysql数据库分页语句
  */
  private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
   //计算第一条记录的位置,Mysql中记录的位置是从0开始的。
//  System.out.println("page:"+page.getPage()+"-------"+page.getRows());
   int offset = (page.getPage() - 1) * page.getRows();
   sqlBuffer.append(" limit ").append(offset).append(",").append(page.getRows());
   return sqlBuffer.toString();
  } 

  /**
  * 获取Oracle数据库的分页查询语句
  * @param page 分页对象
  * @param sqlBuffer 包含原sql语句的StringBuffer对象
  * @return Oracle数据库的分页查询语句
  */
  private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
   //计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
   int offset = (page.getPage() - 1) * page.getRows() + 1;
   sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getRows());
   sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
   //上面的Sql语句拼接之后大概是这个样子:
   //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
   return sqlBuffer.toString();
  } 

  /**
   * 拦截器对应的封装原始对象的方法
   */
  public Object plugin(Object arg0) {
    // TODO Auto-generated method stub
   if (arg0 instanceof StatementHandler) {
      return Plugin.wrap(arg0, this);
    } else {
      return arg0;
    }
  } 

  /**
   * 设置注册拦截器时设定的属性
   */
  public void setProperties(Properties p) {

  }

 public String getDialect() {
 return dialect;
 }

 public void setDialect(String dialect) {
 this.dialect = dialect;
 }

 public String getPageSqlId() {
 return pageSqlId;
 }

 public void setPageSqlId(String pageSqlId) {
 this.pageSqlId = pageSqlId;
 }

}

xml配置:

<!-- MyBatis 接口编程配置 -->
 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   <!-- basePackage指定要扫描的包,在此包之下的映射器都会被搜索到,可指定多个包,包与包之间用逗号或分号分隔-->
   <property name="basePackage" value="com.yidao.mybatis.dao" />
   <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
 </bean>

 <!-- MyBatis 分页拦截器-->
 <bean id="paginationInterceptor" class="com.mybatis.interceptor.PageInterceptor">
   <property name="dialect" value="mysql"/>
   <!-- 拦截Mapper.xml文件中,id包含query字符的语句 -->
    <property name="pageSqlId" value=".*query$"/>
  </bean>

Page类

 package com.yidao.utils;

/**自己看看,需要什么字段加什么字段吧*/
public class Page {

 private Integer rows;

 private Integer page = 1;

 private Integer totalRecord;

 public Integer getRows() {
 return rows;
 }

 public void setRows(Integer rows) {
 this.rows = rows;
 }

 public Integer getPage() {
 return page;
 }

 public void setPage(Integer page) {
 this.page = page;
 }

 public Integer getTotalRecord() {
 return totalRecord;
 }

 public void setTotalRecord(Integer totalRecord) {
 this.totalRecord = totalRecord;
 }

}

ReflectHelper类

package com.yidao.utils;

import java.lang.reflect.Field;

import org.apache.commons.lang3.reflect.FieldUtils;

public class ReflectHelper {

 public static Object getFieldValue(Object obj , String fieldName ){

 if(obj == null){
  return null ;
 }

 Field targetField = getTargetField(obj.getClass(), fieldName);

 try {
  return FieldUtils.readField(targetField, obj, true ) ;
 } catch (IllegalAccessException e) {
  e.printStackTrace();
 }
 return null ;
 }

 public static Field getTargetField(Class<?> targetClass, String fieldName) {
 Field field = null;

 try {
  if (targetClass == null) {
  return field;
  }

  if (Object.class.equals(targetClass)) {
  return field;
  }

  field = FieldUtils.getDeclaredField(targetClass, fieldName, true);
  if (field == null) {
  field = getTargetField(targetClass.getSuperclass(), fieldName);
  }
 } catch (Exception e) {
 }

 return field;
 }

 public static void setFieldValue(Object obj , String fieldName , Object value ){
 if(null == obj){return;}
 Field targetField = getTargetField(obj.getClass(), fieldName);
 try {
  FieldUtils.writeField(targetField, obj, value) ;
 } catch (IllegalAccessException e) {
  e.printStackTrace();
 }
 }
}

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

(0)

相关推荐

  • SpringMvc+Mybatis+Pagehelper分页详解

    最近公司需要做一个告警页面的功能,需要分页,查了很多资料发现PageHelper比较合适 故写一篇从零开始的PageHelper使用的教程,也记录下忙活一天的东西 1.首先需要在项目中添加PageHelper的依赖,这里我用的Maven添加 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>

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

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

  • mybatis分页及模糊查询功能实现

    mybatis中分页有3种方式来实现,通过sql语句(两种传参方式)来实现,通过mybatis 的 Rowbounds 来实现. 通过(自定义类型)传参 来实现分页: 映射文件: <select id="findListBypage" parameterType="cn.wh.util.PageUtil" resultType="Role"> select * from t_role limit #{index},#{size} &l

  • spring boot和mybatis集成分页插件

    MyBatis提供了拦截器接口,我们可以实现自己的拦截器,将其作为一个plugin装入到SqlSessionFactory中. 首先要说的是,Spring在依赖注入bean的时候,会把所有实现MyBatis中Interceptor接口的所有类都注入到SqlSessionFactory中,作为plugin存在.既然如此,我们集成一个plugin便很简单了,只需要使用@Bean创建PageHelper对象即可. 1.添加pom依赖 <dependency> <groupId>com.g

  • mybatis分页插件pageHelper详解及简单实例

    mybatis分页插件pageHelper详解及简单实例 工作的框架spring springmvc mybatis3 首先使用分页插件必须先引入maven依赖,在pom.xml中添加如下 <!-- 分页助手 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5

  • 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分页插件PageHelper的使用详解

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

  • Spring mvc整合mybatis(crud+分页插件)操作mysql

    一.web.xml配置 我们都知道java ee的项目启动的第一件事就是读取web.xml,spring mvc 的web.xml我在上一篇文章中也做了详细讲解,不懂的可以回头看看,讲解的这个项目源码我也会放到github上,也可以去那里看看,这里就不做介绍了. web.xml 配置 <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/c

  • Mybatis实现增删改查及分页查询的方法

    MyBatis的前身就是iBatis.是一个数据持久层(ORM)框架. MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持 久层框架.MyBatis消除了几乎所有的JDBC 代码和参数的手工 设置以及结果集的检索.MyBatis使用简单的XML或注解用于 配置和原始映射,将接口和Java 的POJOs(Plan Old Java Objects,普通的Java 对象)映射成数据库中的记录.每个 MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个 SqlS

  • Java的MyBatis框架中实现多表连接查询和查询结果分页

    实现多表联合查询 还是在david.mybatis.model包下面新建一个Website类,用来持久化数据之用,重写下相应toString()方法,方便测试程序之用. package david.mybatis.model; import java.text.SimpleDateFormat; import java.util.Date; public class Website { private int id; private String name; private int visito

随机推荐