原文地址:xeblog.cn/articles/20

MyBatis插件开发流程

  • 类实现Interceptor接口;
  • 类上添加注解@Intercepts({@Signature(type, method, args)})
    • type:需要拦截的对象,只可取四大对象之一Executor.class、StatementHandler.class、ParameterHandler.class、ResultSetHandler.class
    • method:拦截的对象方法。
    • args:拦截的对象方法参数。
  • 实现拦截的方法Object intercept(Invocation invocation)

Interceptor接口

public interface Interceptor {/*** 此方法将直接覆盖被拦截对象的原有方法** @param invocation 通过该对象可以反射调度拦截对象的方法* @return* @throws Throwable*/Object intercept(Invocation invocation) throws Throwable;/*** 为被拦截对象生成一个代理对象,并返回它** @param target 被拦截的对象* @return*/Object plugin(Object target);/*** 设置插件配置的参数** @param properties 插件配置的参数*/void setProperties(Properties properties);}
复制代码

简单分页插件开发

确定拦截的方法签名

需要在实现Interceptor接口的类上加入@Intercepts({@Signature(type, method, args)})注解才能够运行插件。

type-拦截的对象

  • Executor 执行的SQL 全过程,包括组装参数、组装结果返回和执行SQL的过程等都可以拦截。
  • StatementHandler 执行SQL的过程,拦截该对象可以重写执行SQL的过程。
  • ParameterHandler 执行SQL 的参数组装,拦截该对象可以重写组装参数的规则。
  • ResultSetHandler 执行结果的组装,拦截该对象可以重写组装结果的规则。

对于分页插件,我们只需要拦截StatementHandler对象,重写SELECT类型的SQL语句,实现分页功能。

method-拦截的方法

我们已经能够确定拦截的对象是StatementHandler了,现在我们要确定拦截的是哪个方法,因为StatementHandler是通过prepare方法对SQL进行预编译的,所以我们需要对prepare方法进行拦截,在这个方法执行之前,完成SQL的重新编写(加入limit)。

StatementHandler

public interface StatementHandler {/*** 预编译SQL** @param connection* @return* @throws SQLException*/Statement prepare(Connection connection)throws SQLException;/*** 设置参数** @param statement* @throws SQLException*/void parameterize(Statement statement)throws SQLException;/*** 批处理** @param statement* @throws SQLException*/void batch(Statement statement)throws SQLException;/*** 执行更新操作** @param statement* @return 返回影响行数* @throws SQLException*/int update(Statement statement)throws SQLException;/*** 执行查询操作,将结果交给ResultHandler进行结果的组装** @param statement* @param resultHandler* @param <E>* @return 返回查询的数据列表* @throws SQLException*/<E> List<E> query(Statement statement, ResultHandler resultHandler)throws SQLException;/*** 得到绑定的sql* * @return*/BoundSql getBoundSql();/*** 得到参数处理器* * @return*/ParameterHandler getParameterHandler();}
复制代码

args-拦截的参数

args是一个Class类型的数组,表示的是被拦截方法的参数列表。由于我们已经确定了拦截的是StatementHandlerprepare方法,而该方法只有一个参数Connection,所以我们只需要拦截这一个参数即可。

实现拦截方法

定义一个封装分页参数的类Page

package cn.xeblog.pojo;public class Page {/*** 当前页码*/private Integer pageIndex;/*** 每页数据条数*/private Integer pageSize;/*** 总数据数*/private Integer total;/*** 总页数*/private Integer totalPage;public Page() {}public Page(Integer pageIndex, Integer pageSize) {this.pageIndex = pageIndex;this.pageSize = pageSize;}// 省略get、set方法...
}
复制代码

实现插件分页的功能

package cn.xeblog.plugin;import cn.xeblog.pojo.Page;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class}
)})
public class PagingPlugin implements Interceptor {/*** 默认页码*/private Integer defaultPageIndex;/*** 默认每页数据条数*/private Integer defaultPageSize;public Object intercept(Invocation invocation) throws Throwable {StatementHandler statementHandler = getUnProxyObject(invocation);MetaObject metaObject = SystemMetaObject.forObject(statementHandler);String sql = getSql(metaObject);if (!checkSelect(sql)) {// 不是select语句,进入责任链下一层return invocation.proceed();}BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");Object parameterObject = boundSql.getParameterObject();Page page = getPage(parameterObject);if (page == null) {// 没有传入page对象,不执行分页处理,进入责任链下一层return invocation.proceed();}// 设置分页默认值if (page.getPageIndex() == null) {page.setPageIndex(this.defaultPageIndex);}if (page.getPageSize() == null) {page.setPageSize(this.defaultPageSize);}// 设置分页总数,数据总数setTotalToPage(page, invocation, metaObject, boundSql);// 校验分页参数checkPage(page);return changeSql(invocation, metaObject, boundSql, page);}public Object plugin(Object target) {// 生成代理对象return Plugin.wrap(target, this);}public void setProperties(Properties properties) {// 初始化配置的默认页码,无配置则默认1this.defaultPageIndex = Integer.parseInt(properties.getProperty("default.pageIndex", "1"));// 初始化配置的默认数据条数,无配置则默认20this.defaultPageSize = Integer.parseInt(properties.getProperty("default.pageSize", "20"));}/*** 从代理对象中分离出真实对象** @param invocation* @return*/private StatementHandler getUnProxyObject(Invocation invocation) {// 取出被拦截的对象StatementHandler statementHandler = (StatementHandler) invocation.getTarget();MetaObject metaStmtHandler = SystemMetaObject.forObject(statementHandler);Object object = null;// 分离代理对象while (metaStmtHandler.hasGetter("h")) {object = metaStmtHandler.getValue("h");metaStmtHandler = SystemMetaObject.forObject(object);}return object == null ? statementHandler : (StatementHandler) object;}/*** 判断是否是select语句** @param sql* @return*/private boolean checkSelect(String sql) {// 去除sql的前后空格,并将sql转换成小写sql = sql.trim().toLowerCase();return sql.indexOf("select") == 0;}/*** 获取分页参数** @param parameterObject* @return*/private Page getPage(Object parameterObject) {if (parameterObject == null) {return null;}if (parameterObject instanceof Map) {// 如果传入的参数是map类型的,则遍历map取出Page对象Map<String, Object> parameMap = (Map<String, Object>) parameterObject;Set<String> keySet = parameMap.keySet();for (String key : keySet) {Object value = parameMap.get(key);if (value instanceof Page) {// 返回Page对象return (Page) value;}}} else if (parameterObject instanceof Page) {// 如果传入的是Page类型,则直接返回该对象return (Page) parameterObject;}// 初步判断并没有传入Page类型的参数,返回nullreturn null;}/*** 获取数据总数** @param invocation* @param metaObject* @param boundSql* @return*/private int getTotal(Invocation invocation, MetaObject metaObject, BoundSql boundSql) {// 获取当前的mappedStatement对象MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");// 获取配置对象Configuration configuration = mappedStatement.getConfiguration();// 获取当前需要执行的sqlString sql = getSql(metaObject);// 改写sql语句,实现返回数据总数 $_paging取名是为了防止数据库表重名String countSql = "select count(*) as total from (" + sql + ") $_paging";// 获取拦截方法参数,拦截的是connection对象Connection connection = (Connection) invocation.getArgs()[0];PreparedStatement pstmt = null;int total = 0;try {// 预编译查询数据总数的sql语句pstmt = connection.prepareStatement(countSql);// 构建boundSql对象BoundSql countBoundSql = new BoundSql(configuration, countSql, boundSql.getParameterMappings(),boundSql.getParameterObject());// 构建parameterHandler用于设置sql参数ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(),countBoundSql);// 设置sql参数parameterHandler.setParameters(pstmt);//执行查询ResultSet rs = pstmt.executeQuery();while (rs.next()) {total = rs.getInt("total");}} catch (SQLException e) {e.printStackTrace();} finally {if (pstmt != null) {try {pstmt.close();} catch (SQLException e) {e.printStackTrace();}}}// 返回总数据数return total;}/*** 设置总数据数、总页数** @param page* @param invocation* @param metaObject* @param boundSql*/private void setTotalToPage(Page page, Invocation invocation, MetaObject metaObject, BoundSql boundSql) {// 总数据数int total = getTotal(invocation, metaObject, boundSql);// 计算总页数int totalPage = total / page.getPageSize();if (total % page.getPageSize() != 0) {totalPage = totalPage + 1;}page.setTotal(total);page.setTotalPage(totalPage);}/*** 校验分页参数** @param page*/private void checkPage(Page page) {// 如果当前页码大于总页数,抛出异常if (page.getPageIndex() > page.getTotalPage()) {throw new RuntimeException("当前页码[" + page.getPageIndex() + "]大于总页数[" + page.getTotalPage() + "]");}// 如果当前页码小于总页数,抛出异常if (page.getPageIndex() < 1) {throw new RuntimeException("当前页码[" + page.getPageIndex() + "]小于[1]");}}/*** 修改当前查询的sql** @param invocation* @param metaObject* @param boundSql* @param page* @return*/private Object changeSql(Invocation invocation, MetaObject metaObject, BoundSql boundSql, Page page) throws Exception {// 获取当前查询的sqlString sql = getSql(metaObject);// 修改sql,$_paging_table_limit取名是为了防止数据库表重名String newSql = "select * from (" + sql + ") $_paging_table_limit limit ?, ?";// 设置当前sql为修改后的sqlsetSql(metaObject, newSql);// 获取PreparedStatement对象PreparedStatement pstmt = (PreparedStatement) invocation.proceed();// 获取sql的总参数个数int parameCount = pstmt.getParameterMetaData().getParameterCount();// 设置分页参数pstmt.setInt(parameCount - 1, (page.getPageIndex() - 1) * page.getPageSize());pstmt.setInt(parameCount, page.getPageSize());return pstmt;}/*** 获取当前查询的sql** @param metaObject* @return*/private String getSql(MetaObject metaObject) {return (String) metaObject.getValue("delegate.boundSql.sql");}/*** 设置当前查询的sql** @param metaObject*/private void setSql(MetaObject metaObject, String sql) {metaObject.setValue("delegate.boundSql.sql", sql);}
}
复制代码

配置分页插件

mybatis-config.xml配置文件中配置自定义的分页插件

<plugins><plugin interceptor="cn.xeblog.plugin.PagingPlugin"><property name="default.pageIndex" value="1"/><property name="default.pageSize" value="20"/></plugin>
</plugins>
复制代码

实现DAO

定义POJO对象Role

public class Role {private Long id;private String roleName;private String note;// 省略get、set...
}
复制代码

定义Mapper接口,通过分页对象查询角色列表

public interface RoleMapper {List<Role> listRoleByPage(Page page);
}
复制代码

定义Mapper.xml编写查询的SQL语句

<mapper namespace="cn.xeblog.mapper.RoleMapper"><select id="listRoleByPage" resultType="cn.xeblog.pojo.Role">SELECT id, role_name, note FROM role</select>
</mapper>
复制代码

测试分页插件

测试代码

@Test
public void test() {InputStream inputStream = null;SqlSessionFactory sqlSessionFactory;SqlSession sqlSession = null;try {inputStream = Resources.getResourceAsStream("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);sqlSession = sqlSessionFactory.openSession();RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);// 分页参数,从第一页开始,每页显示5条数据Page page = new Page(1, 5);List<Role> roleList = roleMapper.listRoleByPage(page);System.out.println("===分页信息===");System.out.println("当前页码:" + page.getPageIndex());System.out.println("每页显示数据数:" + page.getPageSize());System.out.println("总数据数:" + page.getTotal());System.out.println("总页数:" + page.getTotalPage());System.out.println("=============");System.out.println("===数据列表===");for (Role role : roleList) {System.out.println(role);}} catch (IOException e) {e.printStackTrace();} finally {if (sqlSession != null) {sqlSession.close();}if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}
}
复制代码

数据库role表中的全部数据信息

id role_name note
1 SUPER_ADMIN 超级管理员
2 admin 管理员
3 user 用户
4 user2 用户2
8 user3 用户3
9 test 测试
10 test2 测试2
11 test3 测试3
12 test4 测试4
13 test5 测试5

代码执行结果

===分页信息===
当前页码:1
每页显示数据数:5
总数据数:10
总页数:2
=============
===数据列表===
Role{id=1, roleName='SUPER_ADMIN', note=' 超级管理员'}
Role{id=2, roleName='admin', note='管理员'}
Role{id=3, roleName='user', note='用户'}
Role{id=4, roleName='user2', note='用户2'}
Role{id=8, roleName='user3', note='用户3'}
复制代码

打印的SQL信息

==>  Preparing: select count(*) as total from (SELECT id, role_name, note FROM role) $_paging
==> Parameters:
<==    Columns: total
<==        Row: 10
<==      Total: 1
==>  Preparing: select * from (SELECT id, role_name, note FROM role) $_paging_table_limit limit ?, ?
==> Parameters: 0(Integer), 5(Integer)
<==    Columns: id, role_name, note
<==        Row: 1, SUPER_ADMIN,  超级管理员
<==        Row: 2, admin, 管理员
<==        Row: 3, user, 用户
<==        Row: 4, user2, 用户2
<==        Row: 8, user3, 用户3
<==      Total: 5
复制代码

参考

  • 《深入浅出MyBatis技术原理与实战》

转载于:https://juejin.im/post/5d08bcbce51d4556bb4cd392

MyBatis插件开发:简单分页插件相关推荐

  1. springboot2.0.5集成mybatis(PageHelper分页插件、generator插件使用)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/zab635590867/article ...

  2. Springboot 系列(十二)使用 Mybatis 集成 pagehelper 分页插件和 mapper 插件

    前言 在 Springboot 系列文章第十一篇里(使用 Mybatis(自动生成插件) 访问数据库),实验了 Springboot 结合 Mybatis 以及 Mybatis-generator 生 ...

  3. Mybatis实现自定义分页插件

    mybatis自定义分页插件,步骤如下[文章末尾关注公众号获取完整代码]: (1)环境搭建 创建一个maven工程,然后引入mybatis依赖和mysql依赖即可. <dependency> ...

  4. spring boot+mybatis+thymeleaf+pagehelper分页插件实现分页功能

    文章目录 前言 正文 业务场景 后端 pom.xml application.yml 实体类video.java和User.java----映射VideoMapper.xml----VideoMapp ...

  5. Mybatis中的分页插件

    目录 一.为什么要使用分页插件? 二.分页常用标签 三.分页插件的使用 1.在mybatis的pom中添加分页插件依赖 2.在mybatis-config.xml中创建分页插件 3.在test文件中进 ...

  6. jquery 简单分页插件jQuerypage

    昨天项目手机端要用到table的分页,考虑到手机端界面小,系统数据不多,在没考虑大批量数据处理的前提前就下载了这个插件,简单. 展示数据datas为json格式. <!DOCTYPE html& ...

  7. Mybatis插件原理和PageHelper结合实战分页插件(七)

    今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理.PageHelper 的官方网站:https://github ...

  8. 【MyBatis】MyBatis分页插件PageHelper的使用

    转载自 https://www.cnblogs.com/shanheyongmu/p/5864047.html 好多天没写博客了,因为最近在实习,大部分时间在熟悉实习相关的东西,也没有怎么学习新的东西 ...

  9. PageHelper 分页插件

    PageHelper(ps:学习记录) PageHelper 是MyBatis 提供的分页插件 目前支持的数据库有 Oracle,Mysql,MariaDB,SQLite,Hsqldb,Postgre ...

  10. 都这么卷了,不懂MyBatis插件开发怎么行,教你实现一个MyBatis分页插件

    MyBatis可谓是Java开发工程师必须要掌握的持久层框架,它能够让我们更容易的通过Java代码操作数据库,并且它还有很高的扩展性,我们可以自定义插件,去让MyBatis的功能变的更为强大,本篇文章 ...

最新文章

  1. MAC 下 安装redis 并配置 php redis 扩展
  2. 最短路径次短路径算法
  3. poj3264Balanced Lineup(倍增ST表)
  4. 月入过万?就靠SEO了
  5. centos7安装samba文件服务器,Centos7.7部署文件共享服务Samba
  6. 不同版本的nutz与log4j2的集成方法
  7. xml标签里有rownum_rownum和order by以及index的关系
  8. C#生成JSON数据格式的函数
  9. [unity]网游中实现资源动态加载
  10. 最新可用快手极速版自动阅读薅羊毛autojs脚本
  11. OSPFv3配置实例
  12. iOS 拼音 Swift K3Pinyin
  13. 虚拟机安装win10提示operating system not found
  14. 什么是光纤?光纤的原理是什么?你能想象没有光纤通讯的世界么?
  15. 《Linux设备驱动开发详解 A》一一3.1 Linux内核的发展与演变
  16. 空间|时间|对象 圈人 + 目标人群透视 - 暨PostgreSQL 10与Greenplum的对比和选择
  17. 大话赛宁云 | 培训服务打造实战型网络安全人才
  18. 我的大学(学习-上)
  19. 【JUC 并发编程】JUC 基本概念
  20. 基础攻防实验-DVWA-秋潮-网络配置

热门文章

  1. RHCE 学习笔记(32) - DNS
  2. 分享一个Winform里面的HTML编辑控件Zeta HTML Edit Control,汉化附源码
  3. 博为峰JavaEE技术文章 ——MyBatis RowBounds分页
  4. 根据TTL值判断目标主机的类型
  5. Hibernate3动态条件查询
  6. linux关闭邮件提示错误,LINUX命令关闭 You have mail in /var/spool/mail/root邮件提醒功能...
  7. DPDK框架原理简介 (0002转)
  8. 使用systemtap调试Linux内核
  9. ffmpeg AVFilter介绍
  10. RTP、RTCP及媒体流同步