在mybatis执行SQL语句之前进行拦击处理实例

比较适用于在分页时候进行拦截。对分页的SQL语句通过封装处理,处理成不同的分页sql。

实用性比较强。

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)

相关推荐

  • Mybatis拦截器的实现介绍

     MyBatis介绍 MyBatis本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .它支持普通 SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索.MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的

  • Mybatis拦截器实现分页

    最终dao层结果: public interface ModelMapper { Page<Model> pageByConditions(RowBounds rowBounds, Model record); } 接下来一步一步来实现分页. 一.创建Page对象: public class Page<T> extends PageList<T> { private int pageNo = 1;// 页码,默认是第一页 private int pageSize = 1

  • java利用mybatis拦截器统计sql执行时间示例

    可以根据执行时间打印sql语句,打印的sql语句是带参数的,可以拷贝到查询分析器什么的直接运行 复制代码 代码如下: package mybatis; import java.text.DateFormat;import java.util.Date;import java.util.List;import java.util.Locale;import java.util.Properties; import org.apache.ibatis.executor.Executor;import

  • java MyBatis拦截器Inteceptor详细介绍

    有许多java初学者对于MyBatis拦截器Inteceptor不是很了解,在这里我来为各位整理下篇关于java中MyBatis拦截器Inteceptor详解, 本文主要分析MyBatis的插件机制,实际就是Java动态代理实现的责任链模式实现. 根据官方文档.Mybatis只允许拦截以下方法,这个决定写拦截器注解签名参数. 代码如下 Executor (update, query, flushStatements, commit, rollback, getTransaction, close

  • MyBatis拦截器实现分页功能实例

    由于业务关系 巴拉巴拉巴拉 好吧 简单来说就是 原来的业务是 需要再实现类里写 selectCount 和selectPage两个方法才能实现分页功能 现在想要达到效果是 只通过一个方法就可以实现 也就是功能合并 所以就有了下面的实践 既然是基于MyBatis 所以就先搭建一个Mybatis的小项目 1.01导入 mybatis和mysql的包 1.02.配置文件 Configuration.xml 中添加 <environments default="development"&

  • MyBatis拦截器:给参数对象属性赋值的实例

    该拦截器的作用:在进行增加.修改等操作时,给数据模型的一些通用操作属性(如:创建人.创建时间.修改人.修改时间等)自动赋值. 该实现是在DAO层拦截,即存入DB前最后一层.后经分析,不是很合理,改为在service层拦截,用spring AOP来实现了,该代码遂弃用.不过已经测试可用,记录备忘. package com.development; import java.lang.reflect.InvocationTargetException; import java.util.Date; i

  • 在mybatis执行SQL语句之前进行拦击处理实例

    比较适用于在分页时候进行拦截.对分页的SQL语句通过封装处理,处理成不同的分页sql. 实用性比较强. 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.e

  • 解决mybatis执行SQL语句部分参数返回NULL问题

    今天在写代码的时候发现一个问题:mybatis执行sql语句的时候返回bean的部分属性为null,在数据库中执行该sql语句能够正常返回,把相关代码反反复复翻了个遍,甚至都重启eclipse了,依旧没解决问题,后来网上搜了一下,还真有类似的问题. 闲话少说,直接说问题,该sql语句是自己写的,resultType直接用了该bean全名称,最终导致部分属性显示为null, 原来的写法: <select id="selectByArticle" parametertype=&quo

  • Mybatis如何直接执行SQL语句

    目录 Mybatis直接执行SQL语句 第一种方法 第二种方法 测试Mybatis执行SQL语句步骤 mybatis核心类:SqlSessionFactory 举例 Mybatis直接执行SQL语句 有时候我们如果要对传入的SQL验证语法方面怎么办呢,首先我们是不是要有一条完整的SQL,而且让mybatis去执行,这是小白最近遇到的,对于用户输入进来的语法与参数,进行拼接并且去执行,判断SQL语句有没有语法错误. 第一种方法 建立一个SQL工具进行SQL处理再返回完整的SQL语句 1.建立工具类

  • 浅谈MyBatis执行SQL的两种方式

    目录 前言 准备接口和Mapper配置文件: 使用SqlSession 发送 SQL 使用 Mapper 接口发送 SQL 比较两种发送 SQL 方式 前言 本文介绍MyBatis执行SQL语句的2种方式:SqlSession和Mapper接口以及它们的区别. 准备接口和Mapper配置文件: 定义UserMapper接口: package cn.cvs.dao; import cn.cvs.pojo.User; import java.util.List; public interface U

  • 在IDEA中安装MyBatis Log Plugin插件,执行mybatis的sql语句(推荐)

    查看代码执行mybatis的sql语句 File–>Settings–>Plugins 搜索 MyBatis Log Plugin Installed安装之后重启,点击上方的Tools就能看到 然后debug执行代码之后 点击启动MyBatis Log Plugin插件 就可以查看每一步执行的sql 到此这篇关于在IDEA中安装MyBatis Log Plugin插件,执行mybatis的sql语句(推荐)的文章就介绍到这了,更多相关idea 安装MyBatis Log Plugin插件内容请

  • java执行SQL语句实现查询的通用方法详解

    完成SQL查询 并将查询结果放入Vector容器,以便其他程序使用 /* * 执行sql查询语句 */ public static <T> Vector<T> executeQuery(Class<T> clazz, String sql, Object... args) { Connection conn = null; PreparedStatement preparedstatement = null; ResultSet rs = null; Vector<

  • MyBatis执行Sql的流程实例解析

    这篇文章主要介绍了MyBatis执行Sql的流程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本博客着重介绍MyBatis执行Sql的流程,关于在执行过程中缓存.动态SQl生成等细节不在本博客中体现,相应内容后面再单独写博客分析吧. 还是以之前的查询作为列子: public class UserDaoTest { private SqlSessionFactory sqlSessionFactory; @Before public v

  • springboot实现执行sql语句打印到控制台

    springboot 执行sql语句打印到控制台 1.简介 每当写完持久化语句时肯定免不了要查漏补缺一波.这里就可以将执行的sql打印到控制台来检查sql语句哪里出了问题. 2.配置 配置非常简单,只需要在配置文件中设置下mapper日志级别就可以了 3.代码 application-test.properties #logging.level.mapper的路径=异常级别 logging.level.com.shuhe360.auth.auth_main_car_api.mapper.CarC

  • C#实现连接SQL Server2012数据库并执行SQL语句的方法

    本文实例讲述了C#实现连接SQL Server2012数据库并执行SQL语句的方法.分享给大家供大家参考,具体如下: 开发工具:Visual Studio 2012 数据库: SQL Server 2012 使用Visual Studio时还是直接和微软自家的SQL Server数据库连接比较方便,就像使用Eclipse时和MySQL连接便捷一样的道理 无论使用什么工具步骤都一样: 1. 首先保证相关工具都已经正确安装了 2. 开启数据库连接服务 3. 在开发工具中通过用户名和口令与数据库进行关

  • 在ASP.NET中用存储过程执行SQL语句

    存储过程执行效率比单独的SQL语句效率高. 样编写存储过程?存储过程在SQL Server 2005对应数据库的可编程性目录下. 比如,创建一个存储过程 复制代码 代码如下: create procedure procNewsSelectNewNews as begin select top 10 n.id,n.title,n.createTime,c.name from news n inner join category c on n.caId=c.id order by n.createT

随机推荐