一、Interceptor介绍

Mybatis 允许用户使用自定义拦截器对SQL语句执行过程中的某一点进行拦截。默认情况,可以拦截的方法如下:

  1. Executor 中的 update()、query()、flushStatement()、commit()、rollback()、getTransaction()、close()、isClosed()方法。
  2. ParameterHandler 中的getParameterObject()方法、setParameters()方法。
  3. ResultSetHandler 中的 handleResultSets()方法、handleOutputParameters()方法。
  4. StatementHandler 中的 prepare() 方法、parameterize()方法、batch()方法、update()方法、query()方法。

Interceptor接口如下:

public interface Interceptor {
// 执行拦截逻辑的方法
Object intercept(Invocation invocation) throws Throwable;
// 决定是否触发intercept()方法
Object plugin(Object target);
// 根据配置初始化Interceptor对象
void setProperties(Properties properties); }

setProperties()方法可以加载mybatis-config.xml配置文件中配置的属性,例如:

  1. <plugins> 

  2. <plugin interceptor="cn.sp.interceptor.PageInterceptor"> 

  3. <property name="testProp" value="100"></property> 

  4. </plugin> 

  5. </plugins> 

用户自定义拦截器的plugin()方法可以使用Mybatis提供的Plugin工具类实现,它实现了InvocationHandler接口,并提供了一个wrap()静态方法用于创建代理对象。

用户自定义的拦截器除了要实现Interceptor接口外,还需要使用 @Intercepts 和 @Signature 注解。

@Intercepts注解中是一个@Signature列表,每个@Signature注解都标识了该插件需要拦截的方法的信息,其中type表示需要拦截的类型,method属性指定具体的方法名,args属性指定了被拦截方法的参数列表。通过这三个属性值就可以表示一个方法签名。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type(); String method(); Class<?>[] args();
}

二、实现PageInterceptor

2.1Mybatis的默认分页机制

Mybatis本身可以使用RowBounds方式进行分页,但是在 DefaultResultSetHandler 中它用的是查询所有数据,然后调用ResultSet.absoulte()方法或循环调用ResultSet.next()方法定位到指定的记录行。这种基于内存分页的方式,当表中的数据量比较大时,会查询全表导致性能问题。

还有一个就是写SQL基于limit实现的物理分页,但是这种基于 "limit offset,length" 的方式如果offset的值很大时,也会导致性能很差,有时间再详细说说mysql分页部分。

下面的例子就是通过自定义拦截器实现物理分页。

2.2代码部分

搭建一个整合Mybatis的SpringBoot项目很简单,过程我就省略了。

PersonDao

public interface PersonDao {

  List<Person> queryPersonsByPage(RowBounds rowBounds);
}

PersonMapper.xml

  1. <?xml version="1.0" encoding="UTF-8" ?> 

  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > 

  3. <mapper namespace="cn.sp.dao.PersonDao" > 


  4. <select id="queryPersonsByPage" resultType="cn.sp.bean.Person"> 

  5. select * from person ORDER BY id DESC 

  6. </select> 

  7. </mapper> 

PageInterceptor

/**
* 利用拦截器实现分页
* Created by 2YSP on 2019/7/7.
*/
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})
})
@Slf4j
public class PageInterceptor implements Interceptor { /**
* Executor.query()方法中,MappedStatement对象在参数列表中的索引位置
*/
private static int MAPPEDSTATEMENT_INDEX = 0; /**
* 用户传入的实参对象在参数列表中的索引位置
*/
private static int PARAMTEROBJECT_INDEX = 1;
/**
* 分页对象在参数列表中的索引位置
*/
private static int ROWBOUNDS_INDEX = 2; /**
* 执行拦截逻辑的方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 参数列表
Object[] args = invocation.getArgs();
final MappedStatement mappedStatement = (MappedStatement) args[MAPPEDSTATEMENT_INDEX];
final Object parameter = args[PARAMTEROBJECT_INDEX];
final RowBounds rowBounds = (RowBounds) args[ROWBOUNDS_INDEX];
// 获取offset,即查询的起始位置
int offset = rowBounds.getOffset();
int limit = rowBounds.getLimit();
// 获取BoundSql对象,其中记录了包含"?"占位符的SQL语句
final BoundSql boundSql = mappedStatement.getBoundSql(parameter);
// 获取BoundSql中记录的SQL语句
String sql = boundSql.getSql();
sql = getPagingSql(sql, offset, limit);
log.info("==========sql:\n" + sql);
// 重置RowBounds对象
args[ROWBOUNDS_INDEX] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
// 根据当前语句创建新的MappedStatement
args[MAPPEDSTATEMENT_INDEX] = createMappedStatement(mappedStatement, boundSql, sql);
// 通过Invocation.proceed()方法调用被拦截的Executor.query()方法
return invocation.proceed();
} private Object createMappedStatement(MappedStatement mappedStatement, BoundSql boundSql,
String sql) {
// 创建新的BoundSql对象
BoundSql newBoundSql = createBoundSql(mappedStatement, boundSql, sql);
Builder builder = new Builder(mappedStatement.getConfiguration(), mappedStatement.getId(),
new BoundSqlSqlSource(newBoundSql), mappedStatement.getSqlCommandType());
builder.useCache(mappedStatement.isUseCache());
builder.cache(mappedStatement.getCache());
builder.databaseId(mappedStatement.getDatabaseId());
builder.fetchSize(mappedStatement.getFetchSize());
builder.flushCacheRequired(mappedStatement.isFlushCacheRequired()); builder.keyColumn(delimitedArrayToString(mappedStatement.getKeyColumns()));
builder.keyGenerator(mappedStatement.getKeyGenerator());
builder.keyProperty(delimitedArrayToString(mappedStatement.getKeyProperties())); builder.lang(mappedStatement.getLang());
builder.resource(mappedStatement.getResource()); builder.parameterMap(mappedStatement.getParameterMap());
builder.resultMaps(mappedStatement.getResultMaps());
builder.resultOrdered(mappedStatement.isResultOrdered());
builder.resultSets(delimitedArrayToString(mappedStatement.getResultSets()));
builder.resultSetType(mappedStatement.getResultSetType()); builder.timeout(mappedStatement.getTimeout());
builder.statementType(mappedStatement.getStatementType()); return builder.build(); } public String delimitedArrayToString(String[] array) {
String result = "";
if (array == null || array.length == 0) {
return result;
}
for (int i = 0; i < array.length; i++) {
result += array[i];
if (i != array.length - 1) {
result += ",";
}
}
return result;
} class BoundSqlSqlSource implements SqlSource { private BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
} @Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
} private BoundSql createBoundSql(MappedStatement mappedStatement, BoundSql boundSql, String sql) {
BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), sql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
return newBoundSql;
} /**
* 重写sql
*/
private String getPagingSql(String sql, int offset, int limit) {
sql = sql.trim();
boolean hasForUpdate = false;
String forUpdatePart = "for update";
if (sql.toLowerCase().endsWith(forUpdatePart)) {
// 将当前SQL语句的"for update片段删除"
sql = sql.substring(0, sql.length() - forUpdatePart.length());
hasForUpdate = true;
} StringBuilder result = new StringBuilder();
result.append(sql);
result.append(" limit ");
result.append(offset);
result.append(",");
result.append(limit); if (hasForUpdate) {
result.append(" " + forUpdatePart);
}
return result.toString();
} /**
* 决定是否触发intercept()方法
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} /**
* 根据配置初始化Interceptor对象
*/
@Override
public void setProperties(Properties properties) {
log.info("properties: " + properties.getProperty("testProp"));
}
}

这里的思路就是拦截 query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) 方法或query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) 方法,通过RowBounds对象获得所需记录的offset和limit,通过BoundSql获取待执行的sql语句,最后重写SQL语句加入"limit offset,length"实现分页。

三、测试

执行如下测试方法:

分页测试

控制台显示结果:

enter description here

输出结果是,id为7的王五和id为6的张三,再对比数据库数据。

enter description here

最后得出结论成功实现分页查询,GitHub上开源的大名鼎鼎的PageHelper也是利用拦截器插件实现的,有时间要再看下它的源码实现。

本文代码点击这里

Mybatis利用Intercepter实现物理分页的更多相关文章

  1. spring+mybatis利用interceptor(plugin)兑现数据库读写分离

    使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ...

  2. Mybatis利用拦截器做统一分页

    mybatis利用拦截器做统一分页 查询传递Page参数,或者传递继承Page的对象参数.拦截器查询记录之后,通过改造查询sql获取总记录数.赋值Page对象,返回. 示例项目:https://git ...

  3. springboot整合mybatis,利用mybatis-genetor自动生成文件

    springboot整合mybatis,利用mybatis-genetor自动生成文件 项目结构: xx 实现思路: 1.添加依赖 <?xml version="1.0" e ...

  4. mybatis: 利用多数据源实现分库存储

    之前写过一篇mybatis 使用经验小结 提到过多数据源的处理方式,虽然简单但是姿势不太优雅,今天介绍一些更美观的办法: spring中有一个AbstractRoutingDataSource的抽象类 ...

  5. Mybatis pageHelper.startPage(...)是物理分页

    使用PageHelper.startPage(...)进行物理分页 业务需求只显示其中的100条数据 之前是在业务逻辑里对参数limit进行了处理 后来试试sql的limit查询100条数据 但是不确 ...

  6. spring 整合 mybatis (不含物理分页)

    http://www.mybatis.org/spring/mappers.html http://www.mybatis.org/spring/zh/mappers.html <?xml ve ...

  7. Spring+SpringMVC+Mybatis 利用AOP自定义注解实现可配置日志快照记录

    http://my.oschina.net/ydsakyclguozi/blog/413822

  8. mybatis利用generator自动生成的代码

    /** * 排序规则 */ protected String orderByClause; /** * 去重规则 */ protected boolean distinct; /** * where条 ...

  9. MyBatis的笔记

    1.#{}和${}的区别是什么? #{}是预编译处理,${}是字符串替换. #{}是sql的参数占位符,${}是Properties文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替 ...

随机推荐

  1. docker数据持久化/共享---volume,bind-mount,tmpfs-mount

    一.将Docker数据挂载到容器 在Docker中,要想实现数据的持久化(所谓Docker的数据持久化即数据不随着Container的结束而结束),需要将数据从宿主机挂载到容器中.目前Docker提供 ...

  2. 第15.5节 PyQt的历史沿革介绍

    当朋友向我推荐PyQt时,老猿才知道有这样一个在Python下的开源的图形界面开发工具,当准备学习PyQt安装时,发现要安装sip.PyQt.PyQt-tools,然后还要进行相关配置.老猿很好奇为什 ...

  3. SZhe_Scan碎遮:一款基于Flask框架的web漏洞扫描神器

    SZhe_Scan碎遮:一款基于Flask框架的web漏洞扫描神器 天幕如遮,唯我一刀可碎千里华盖,纵横四海而无阻,是谓碎遮 --取自<有匪> 写在前面 这段时间很多时间都在忙着编写该项目 ...

  4. python-字典dict、去除重复set

    dict Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度. 举个例子,假设要根据同学的名字 ...

  5. 我摊牌了,大厂面试Linux就这5个问题

    说真的,这就是<我想进大厂>系列第八篇,但是Linux的问题确实很少,就这样,强行编几个没有营养的问题也没啥意义. 1.CPU负载和CPU利用率的区别是什么? 首先,我们可以通过uptim ...

  6. 图 Graph

    本文主要内容为:图的定义以及基本术语 图的定义 图 图G的组成:由 数据元素的集合E 和 数据间的关系集合E 组成,记作:G = <V, E> 顶点 (vertex):数据元素,V就是顶点 ...

  7. 冲刺Day3

    每天举行站立式会议照片: 昨天已完成的工作: 1.完成登录注册的后台代码. 2.确定商品查找的接口. 3.尝试与数据库连接. 今天计划完成的工作: 成员 任务 高嘉淳 完成用户信息管理的部分功能 覃泽 ...

  8. JQuery统一复写美化项目中所有radio单选按钮样式

    老项目要升级改版,对于分散在各页面的样式不好处理,怕有遗漏,尤其是优化input表单,修改其默认样式,接下来,我将给大家分享一下,我在项目中的总结. 效果 上代码: 1.简单搞一搞 CSS,此处代码有 ...

  9. 20201205-3 HTML环境搭建与文件基本结构

      HTML环境搭建与文件基本结构   HTML的基础 HTML环境搭建 Pycharm 包含全部环境  编写(代码) →  运行浏览器 → 代码检查 Sublime  只是用来编写 HTML 代码: ...

  10. IntelliJ IDEA(十二) :IDEA常用配置

    idea版本 2019.3.4 配置JDK File--> Project Structure... 修改项目 jdk和项目语言等级 修改jdk版本 File--> Ohter Setti ...