在mybatis执行SQL语句之前进行拦击处理
转载自:http://blog.csdn.net/hfmbook/article/details/41985853
比较适用于在分页时候进行拦截。对分页的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>
在mybatis执行SQL语句之前进行拦击处理的更多相关文章
- springmvc 项目完整示例04 整合mybatis mybatis所需要的jar包 mybatis配置文件 sql语句 mybatis应用
百度百科: MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBat ...
- 10.1(java学习笔记)JDBC基本操作(连接,执行SQL语句,获取结果集)
一.JDBC JDBC的全称是java database connection java数据库连接. 在java中需要对数据库进行一系列的操作,这时就需要使用JDBC. sun公司制定了关于数据库操作 ...
- Java-MyBatis:MyBatis 3 | SQL 语句构建器类
ylbtech-Java-MyBatis:MyBatis 3 | SQL 语句构建器类 1.返回顶部 1. SQL语句构建器类 问题 Java程序员面对的最痛苦的事情之一就是在Java代码中嵌入SQL ...
- 4.5 .net core下直接执行SQL语句并生成DataTable
.net core可以执行SQL语句,但是只能生成强类型的返回结果.例如var blogs = context.Blogs.FromSql("SELECT * FROM dbo.Blogs& ...
- 三种执行SQL语句的的JAVA代码
问题描述: 连接数据库,执行SQL语句是必不可少的,下面给出了三种执行不通SQL语句的方法. 1.简单的Statement执行SQL语句.有SQL注入,一般不使用. public static voi ...
- Entity Framework Code First执行SQL语句、视图及存储过程
1.Entity Framework Code First查询视图 Entity Framework Code First目前还没有特别针对View操作的方法,但对于可更新的视图,可以采用与Table ...
- EFCore执行Sql语句的方法:FromSql与ExecuteSqlCommand
前言 在EFCore中执行Sql语句的方法为:FromSql与ExecuteSqlCommand:在EF6中的为SqlQuery与ExecuteSqlCommand,而FromSql和SqlQuery ...
- sp_executesql得到执行sql语句的返回值
执行 sql语句,得到 变量的值 ' declare @Partition int; ); ); SET @SQLString = N'SELECT @RangeKeyOUT = $PARTITION ...
- EF中执行sql语句,以及事务
EF to sql string sql = "select T_Task.BSID,T_Task.CloseDate,T_Task.CompleteDate,T_Task.CloseUse ...
随机推荐
- UCML平台中 如何设置列表单元格中的链接失效
解决方案: 找到“a.datagrid-cell-bclink”,麻烦的是这个标记是由js动态加载的,需要等待这个加载完成:等加载完成后,删除a标记“$(“a.datagrid-cell-bclink ...
- asp.net多图片上传实现程序代码
下面是一个完整的asp.net同时支持多图片上传一个实现,有需要的朋友可参考一下,本文章限制同时可上传8张图片,当然大可自己可修改更多或更少. 前台代码如下: 复制代码代码如下: <% @ Pa ...
- PHP:parse_str()字符串函数
parse_str()-把字符串解析成多个变量. 描述:void parse_str(sring $str [, array $arr]) 如果str是URL传递入的查询字符串(query stri ...
- 卸载CentOS 5.4自带的OpenJDK,配置新的Java环境
本文CentOS版本为5.4 final,使用图形界面与命令结合的操作方式,由于CentOS 5.4在默认情况下,会安装OpenOffice之类的软件,而这些软件需要Java支持,因此系统会默认安装一 ...
- Laravel 5 基础(四)- Blade 简介
在多个页面中我们可能包含相同的内容,像是文件头,链接的css或者js等.我们可以利用布局文件完成这个功能. 让我们新建一个布局文件,例如 views/layout.blade.php <!doc ...
- How to change comment
AX2009 // USR Changed on 2013-07-10 at 12:57:46 by 7519 - Begin // USR Changed on 2013-07-10 at 12:5 ...
- 在Java中执行js代码
在某些特定场景下,我们需要用Java来执行Js代码(如模拟登录时,密码被JS加密了的情况),操作如下: ScriptEngineManager mgr = new ScriptEngineManage ...
- RMAN备份失败之:mount: block device /dev/emcpowerc1 is write-protected, mounting read-only
今天再做巡检的时候发现有一台服务器的RMAN备份不正常,有一段时间没能正常备份了.检查了一下脚本,正常,定时任务列表也正常,再检查一下/var/log/cron的内容,也没有问题.尝试在该挂载点上创建 ...
- 关于sql语句in的使用注意规则
想必大家都用过sql中的in语句吧,我这里描述下我遇到的一种in语句问题,并总结一些给大家分享下,不对的地方还希望大虾指点下. 问题描述:IN子查询时,子查询中字段在表中不存在时语句却不报错 平常工作 ...
- 十二、BOOL冒泡
int main(){ int a[5] = {5,2,3,4,1}; //需要一个可以告诉我们没有交换的东西 //YES:交换 //NO:未交换 ...