在mybatis执行SQL语句之前进行拦击处理实例
发布时间 - 2026-01-11 00:48:12 点击率:次比较适用于在分页时候进行拦截。对分页的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();
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# mybatis拦击
# mybatis
# SQL进行拦击
# MyBatis 执行动态 SQL语句详解
# MyBatis一次执行多条SQL语句的操作
# Mybatis如何直接执行SQL语句
# Java MyBatis是如何执行一条SQL语句的
# 浅谈MyBatis 如何执行一条 SQL语句
# MyBatis中常见的SQL执行方式及其使用方法
# 分页
# 拦截器
# 都是
# 是从
# 建立一个
# 第一条
# 是一个
# 这是
# 就会
# 是在
# 多个
# 子类
# 在此
# 要有
# 两种
# 而在
# 适用于
# 把它
# 对其
# 要做
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel怎么清理缓存_Laravel optimize clear命令详解
HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】
Laravel控制器是什么_Laravel MVC架构中Controller的作用与实践
详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)
微信小程序 scroll-view组件实现列表页实例代码
奇安信“盘古石”团队突破 iOS 26.1 提权
jQuery 常见小例汇总
厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?
rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted
java中使用zxing批量生成二维码立牌
制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?
什么是JavaScript解构赋值_解构赋值有哪些实用技巧
Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】
如何基于云服务器快速搭建个人网站?
如何实现javascript表单验证_正则表达式有哪些实用技巧
如何快速搭建自助建站会员专属系统?
制作企业网站建设方案,怎样建设一个公司网站?
Laravel如何创建自定义中间件?(Middleware代码示例)
jimdo怎样用html5做选项卡_jimdo选项卡html5实现与切换效果【指南】
Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧
简单实现jsp分页
如何自定义safari浏览器工具栏?个性化设置safari浏览器界面教程【技巧】
利用JavaScript实现拖拽改变元素大小
Laravel如何使用Passport实现OAuth2?(完整配置步骤)
郑州企业网站制作公司,郑州招聘网站有哪些?
如何自己制作一个网站链接,如何制作一个企业网站,建设网站的基本步骤有哪些?
Laravel如何优雅地处理服务层_在Laravel中使用Service层和Repository层
制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
Android利用动画实现背景逐渐变暗
Swift中swift中的switch 语句
三星网站视频制作教程下载,三星w23网页如何全屏?
免费视频制作网站,更新又快又好的免费电影网站?
Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤
Windows10电脑怎么设置虚拟光驱_Win10右键装载ISO镜像文件
香港服务器建站指南:免备案优势与SEO优化技巧全解析
Laravel如何使用Gate和Policy进行权限控制_Laravel权限判定与策略规则配置
Laravel如何与Docker(Sail)协同开发?(环境搭建教程)
如何在建站主机中优化服务器配置?
长沙做网站要多少钱,长沙国安网络怎么样?
高端建站如何打造兼具美学与转化的品牌官网?
北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?
大同网页,大同瑞慈医院官网?
Laravel如何设置自定义的日志文件名_Laravel根据日期或用户ID生成动态日志【技巧】
Javascript中的事件循环是如何工作的_如何利用Javascript事件循环优化异步代码?
Laravel怎么实现支付功能_Laravel集成支付宝微信支付
如何快速生成专业多端适配建站电话?
头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?
百度浏览器如何管理插件 百度浏览器插件管理方法
米侠浏览器网页图片不显示怎么办 米侠图片加载修复

