在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> 
时间: 2024-12-15 01:47:00

在mybatis执行SQL语句之前进行拦击处理的相关文章

2.监控mybatis执行sql语句

1.在项目中的 resource资源文件夹中创建一个文件 监控等级有五个  ERROR --->WARN---->INFO----->DEBUG----->TRACE 监控包名为 com.gzcgxt.erp下面的所有子包 # Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... # ERROP > WARN > INFO > D

基于SpringJDBC的类mybatis形式SQL语句管理的思考与实现

SpringJDBC为我们提供了一个非常方便的数据库访问接口,我们都知道使用JdbcTemplate对数据库进行操作时需要传入执行的SQL语句.在小型系统中,SQL语句可能并不会太多,这个时候我们无论采取什么方式进行管理都没有关系.但是当系统逐渐庞大后,我们就要考虑以一种恰当的方式对这些SQL进行管理了.我们将首先介绍比较常见的几种SQL管理方式,然后再讨论类mybatis形式的SQL管理方式. 在方法中直接构造并传入 这种方式是在需要执行数据库操作的方法内直接硬编码SQL语句.这样做的好处在于

Java实战之路(1):SpringBoot项目中使用Mybatis打印Sql语句

SpringBoot项目中使用Mybatis打印Sql语句 如题,实际项目中使用很多都会用到SpringBoot+Mybatis的经典搭配进行开发,数据库里明明有数据,可是程序运行就是查不到,此时我们在本地Debug时,需要将Mybatis的实际Sql打印出来,看看Sql与我们期望的是否一致,或者将Sql拿到数据库中直接执行,看看结果.这里简单介绍几种实战中的用法. 方法一 properties:在application.properties配置文件中增加如下配置 logging.level.c

JDBC系列:(3)使用PreparedStatement执行sql语句

执行sql语句的接口 接口 作用 Statement接口 用于执行静态的sql语句 PreparedStatement接口 用于执行预编译sql语句 CallableStatement接口 用于执行存储过程的sql语句(call xxx) PreparedStatement Vs Statement 序号 不同 描述 1 语法不同 PreparedStatement可以使用预编译的sql,而Statment只能使用静态的sql 2 效率不同 PreparedStatement可以使用sql缓存区

循环执行sql语句

DECLARE--声明变量SQL_ALLTABLES LONG; SQL_INSERT LONG; TYPE THE_CURSOR_TYPE IS REF CURSOR; --定义引用游标的数据类型CURSOR_D THE_CURSOR_TYPE; --定义游标 DATAUP VARCHAR2(200);BEGIN--井筒文档SQL_ALLTABLES := 'SELECT DISTINCT (TABLE_NAME) FROM USER_TAB_COLUMNS WHERE COLUMN_NAME

EF中执行sql语句

EF原理 EF 会自动把 Where().OrderBy().Select()等这些编译成"表达式树(Expression Tree)",然后会把表达式树翻译成 SQL 语句去执行.(编译原理,AST)因此不是"把数据都取到内存中,然后使用集合的方法进行数据过滤",因此性能不会低.但是如果这个操作不能被翻译成 SQL 语句,则或者报错,或者被放到内存中操作,性能就会非常低 跟踪EF的查询Sql语句: DbContext 有一个 Database 属性,其中的 Log

EntityFramework执行SQL语句

在EF中执行Sql语句. using (var context = new EFRecipesEntities()) { string sql = @"insert into Chapter3.Payment(Amount, Vendor) values (@Amount, @Vendor)"; var args = new DbParameter[] { new SqlParameter { ParameterName = "Amount", Value = 99

linux程序设计——执行SQL语句(第八章)

8.3    使用C语言访问MySQL数据 8.3.3 执行SQL语句 执行SQL语句的主要API函数被恰当的命名为: int mysql_query(MYSQL *connection, const char *query); 这个例程接受连接结构指针和文本字符串形式的有效SQL语句,如果成功,它返回0. 1.不返回数据的SQL语句 为简单起见,先看一些不返回任何数据的SQL语句:UPDATE,DELETE和INSERT. 下面的函数用于检查受查询影响的行数: my_ulonglong mys

怎样在dos里进入mysql,执行sql语句

1.进入mysql bin目录下,执行mysql.exe 2.用mysql -uroot -p登陆mysql之后就可以使用 怎样在dos里进入mysql,执行sql语句,布布扣,bubuko.com