MyBatis分页插件

目录

分页插件

Mybatis插件典型适用场景

实现思考

第一个问题

第二个问题

自定义分页插件

分页插件使用

添加pom依赖

插件注册

调用

代理和拦截是怎么实现的

PageHelper 原理


分页插件

MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能。需要注意的是,如果没有完全理解MyBatis 的运行原理和插件的工作方式,最好不要使用插件,因为它会改变系底层的工作逻辑,给系统带来很大的影响。

MyBatis 的插件可以在不修改原来的代码的情况下,通过拦截的方式,改变四大核心对象的行为,比如处理参数,处理SQL,处理结果。

Mybatis插件典型适用场景

分页功能

mybatis的分页默认是基于内存分页的(查出所有,再截取),数据量大的情况下效率较低,不过使用mybatis插件可以改变该行为,只需要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句即可;

公共字段统一赋值

一般业务系统都会有创建者,创建时间,修改者,修改时间四个字段,对于这四个字段的赋值,实际上可以在DAO层统一拦截处理,可以用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值即可;

性能监控

对于SQL语句执行的性能监控,可以通过拦截Executor类的update, query等方法,用日志记录每个方法执行的时间;

其它

其实mybatis扩展性还是很强的,基于插件机制,基本上可以控制SQL执行的各个阶段,如执行阶段,参数处理阶段,语法构建阶段,结果集处理阶段,具体可以根据项目业务来实现对应业务逻辑。

实现思考

第一个问题

 不修改对象的代码,怎么对对象的行为进行修改,比如说在原来的方法前面做一点事情,在原来的方法后面做一点事情?

  答案:大家很容易能想到用代理模式,这个也确实是MyBatis 插件的原理。

第二个问题

我们可以定义很多的插件,那么这种所有的插件会形成一个链路,比如我们提交一个休假申请,先是项目经理审批,然后是部门经理审批,再是HR 审批,再到总经理审批,怎么实现层层的拦截?

  答案:插件是层层拦截的,我们又需要用到另一种设计模式——责任链模式。

在之前的源码中我们也发现了,mybatis内部对于插件的处理确实使用的代理模式,既然是代理模式,我们应该了解MyBatis 允许哪些对象的哪些方法允许被拦截,并不是每一个运行的节点都是可以被修改的。只有清楚了这些对象的方法的作用,当我们自己编写插件的时候才知道从哪里去拦截。在MyBatis 官网有答案,我们来看一下:

mybatis – MyBatis 3 | 配置

Executor 会拦截到CachingExcecutor 或者BaseExecutor。因为创建Executor 时是先创建CachingExcecutor,再包装拦截。从代码顺序上能看到。我们可以通过mybatis的分页插件来看看整个插件从包装拦截器链到执行拦截器链的过程。

  在查看插件原理的前提上,我们需要来看看官网对于自定义插件是怎么来做的,官网上有介绍:通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。这里本人踩了一个坑,在Springboot中集成,同时引入了pagehelper-spring-boot-starter 导致RowBounds参数的值被刷掉了,也就是走到了我的拦截其中没有被设置值,这里需要注意,拦截器出了问题,可以Debug看一下Configuration配置类中拦截器链的包装情况。

自定义分页插件

@Intercepts({@Signature(type = Executor.class,method = "query" ,args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ), // 需要代理的对象和方法@Signature(type = Executor.class,method = "query" ,args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} ) // 需要代理的对象和方法
})
public class MyPageInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("简易版的分页插件:逻辑分页改成物理分页");// 修改sql 拼接Limit 0,10Object[] args = invocation.getArgs();// MappedStatement 对mapper映射文件里面元素的封装MappedStatement ms= (MappedStatement) args[0];// BoundSql 对sql和参数的封装Object parameterObject=args[1];BoundSql boundSql = ms.getBoundSql(parameterObject);// RowBounds 封装了逻辑分页的参数 :当前页offset,一页数limitRowBounds rowBounds= (RowBounds) args[2];// 拿到原来的sql语句String sql = boundSql.getSql();String limitSql=sql+ " limit "+rowBounds.getOffset()+","+ rowBounds.getLimit();//将分页sql重新封装一个BoundSql 进行后续执行BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), limitSql, boundSql.getParameterMappings(), parameterObject);// 被代理的对象Executor executor= (Executor) invocation.getTarget();CacheKey cacheKey = executor.createCacheKey(ms, parameterObject, rowBounds, pageBoundSql);// 调用修改过后的sql继续执行查询return  executor.query(ms,parameterObject,rowBounds, (ResultHandler) args[3],cacheKey,pageBoundSql);}
}

拦截签名跟参数的顺序有严格要求,如果按照顺序找不到对应方法会抛出异常:

org.apache.ibatis.exceptions.PersistenceException: ### Error opening session. Cause: org.apache.ibatis.plugin.PluginException: Could not find method on interface org.apache.ibatis.executor.Executor named query

MyBatis 启动时扫描 标签, 注册到Configuration 对象的 InterceptorChain 中。property 里面的参数,会调用setProperties()方法处理。

分页插件使用

添加pom依赖

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>1.2.15</version>
</dependency>

插件注册

在mybatis-config.xml 中注册插件

<configuration><plugins><!-- com.github.pagehelper为PageHelper类所在包名 --><plugin interceptor="com.github.pagehelper.PageHelper"><property name="helperDialect" value="mysql" /><!-- 该参数默认为false --><!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 --><!-- 和startPage中的pageNum效果一样 --><property name="offsetAsPageNum" value="true" /><!-- 该参数默认为false --><!-- 设置为true时,使用RowBounds分页会进行count查询 --><property name="rowBoundsWithCount" value="true" /><!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 --><!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) --><property name="pageSizeZero" value="true" /><!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 --><!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 --><!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 --><property name="reasonable" value="true" /><!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 --><!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 --><!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 --><!-- 不理解该含义的前提下,不要随便复制该配置 --><property name="params" value="pageNum=start;pageSize=limit;" /></plugin></plugins>
</configuration>

调用

// 获取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
// 通过加载配置文件获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// Mybatis在getMapper就会给我们创建jdk动态代理EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);PageHelper.startPage(1, 5);List<Emp> list=mapper.selectAll(); PageInfo<ServiceStation> info = new PageInfo<ServiceStation>(list, 3);                   System.out.println("当前页码:"+info.getPageNum());System.out.println("每页的记录数:"+info.getPageSize());System.out.println("总记录数:"+info.getTotal());System.out.println("总页码:"+info.getPages());System.out.println("是否第一页:"+info.isIsFirstPage());System.out.println("连续显示的页码:");int[] nums = info.getNavigatepageNums();for (int i = 0; i < nums.length; i++) {System.out.println(nums[i]);}     
}  

代理和拦截是怎么实现的

上面提到的可以被代理的四大对象都是什么时候被代理的呢?Executor 是openSession() 的时候创建的; StatementHandler 是SimpleExecutor.doQuery()创建的;里面包含了处理参数的ParameterHandler 和处理结果集的ResultSetHandler 的创建,创建之后即调用InterceptorChain.pluginAll(),返回层层代理后的对象。代理是由Plugin 类创建。在我们重写的 plugin() 方法里面可以直接调用returnPlugin.wrap(target, this);返回代理对象。

当个插件的情况下,代理能不能被代理?代理顺序和调用顺序的关系? 可以被代理。

因为代理类是Plugin,所以最后调用的是Plugin 的invoke()方法。它先调用了定义的拦截器的intercept()方法。可以通过invocation.proceed()调用到被代理对象被拦截的方法。

调用流程时序图:

PageHelper 原理

先来看一下分页插件的简单用法:

PageHelper.startPage(1, 3);

List<Blog> blogs = blogMapper.selectBlogById2(blog); PageInfo page = new PageInfo(blogs, 3);

对于插件机制我们上面已经介绍过了,在这里我们自然的会想到其所涉及的核心类 :PageInterceptor。拦截的是Executor 的两个query()方法,要实现分页插件的功能,肯定是要对我们写的sql进行改写,那么一定是在 intercept 方法中进行操作的,我们会发现这么一行代码:

String pageSql = this.dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);

 调用到 AbstractHelperDialect 中的  getPageSql 方法:

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {// 获取sqlString sql = boundSql.getSql();//获取分页参数对象Page page = this.getLocalPage();return this.getPageSql(sql, page, pageKey);}

这里可以看到会去调用 this.getLocalPage(),我们来看看这个方法:

public <T> Page<T> getLocalPage() {return PageHelper.getLocalPage();
}
//线程独享
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
public static <T> Page<T> getLocalPage() {return (Page)LOCAL_PAGE.get();
}

可以发现这里是调用的是PageHelper的一个本地线程变量中的一个 Page对象,从其中获取我们所设置的  PageSize 与 PageNum,那么他是怎么设置值的呢?请看:

PageHelper.startPage(1, 3);public static <E> Page<E> startPage(int pageNum, int pageSize) {return startPage(pageNum, pageSize, true);
}public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {Page<E> page = new Page(pageNum, pageSize, count);page.setReasonable(reasonable);page.setPageSizeZero(pageSizeZero);Page<E> oldPage = getLocalPage();if (oldPage != null && oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}//设置页数,行数信息setLocalPage(page);return page;
}protected static void setLocalPage(Page page) {//设置值LOCAL_PAGE.set(page);
}

在我们调用 PageHelper.startPage(1, 3); 的时候,系统会调用 LOCAL_PAGE.set(page) 进行设置,从而在分页插件中可以获取到这个本地变量对象中的参数进行 SQL 的改写,由于改写有很多实现,我们这里用的Mysql的实现:

在这里我们会发现分页插件改写SQL的核心代码,这个代码就很清晰了,不必过多赘述:

public String getPageSql(String sql, Page page, CacheKey pageKey) {StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);sqlBuilder.append(sql);if (page.getStartRow() == 0) {sqlBuilder.append(" LIMIT ");sqlBuilder.append(page.getPageSize());} else {sqlBuilder.append(" LIMIT ");sqlBuilder.append(page.getStartRow());sqlBuilder.append(",");sqlBuilder.append(page.getPageSize());pageKey.update(page.getStartRow());}pageKey.update(page.getPageSize());return sqlBuilder.toString();
}

PageHelper 就是这么一步一步的改写了我们的SQL 从而达到一个分页的效果。

关键类总结:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/256291.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Mybatis——分页

1.为什么要分页&#xff1f; 减少数据的处理量使用Limit分页 select * from user limit startIndex,pageSize;使用Mybatis实现分页&#xff0c;核心SQL 1.数据库文件-db.properties drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/mybatis?useSSLfalse&…

Mybatis实现分页的三种方式

文章目录 1、Limit实现分页2、RowBounds分页&#xff08;不建议使用&#xff09;3、MyBatis分页插件PageHelper&#xff08;了解即可&#xff09; 1、Limit实现分页 sql语句 SELECT * from user limit startIndex,pageSize简单示例&#xff1a; user表 查询一&#xff1a;从第…

Mybatis的四种分页方式详解

LIMIT关键字 mapper代码 select * from tb_user limit #{pageNo}, #{pageSize} 业务层直接调用 public List findByPageInfo(PageInfo info) { return userMapper.selectByPageInfo(info); } 3&#xff0c;优点 灵活性高&#xff0c;可优化空间大 mysql分页语句优化 4&…

mybatis实现分页的几种方式

本文目录 借助数组进行分页借助Sql语句进行分页拦截器分页RowBounds实现分页 借助数组进行分页 原理&#xff1a;进行数据库查询操作时&#xff0c;获取到数据库中所有满足条件的记录&#xff0c;保存在应用的临时数组中&#xff0c;再通过List的subList方法&#xff0c;获取到…

【分布式应用】ELK企业级日志分析系统

一、ELK 简介 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kiabana 三个开源工具配合使用&#xff0c; 完成更强大的用户对日志的查询、排序、统计需求。 1.1 ELK各组件介绍 ElasticSearch&#xff1a; 是基于Lucene&#xff08;一个…

Rust每日一练(Leetday0016) 全排列I\II、旋转图像

目录 46. 全排列 Permutations &#x1f31f;&#x1f31f; 47. 全排列 II Permutations II &#x1f31f;&#x1f31f; 48. 旋转图像 Rotate Image &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专…

win10操作系统官网如何下载ios境像文件安装操作系统

1.打开官网 2.立即下载工具 3.正在准备进行工作 4.接收条款 5.根据需求选择安装合适的位置 6.等待创建成功 7.右键选择装载 8.双击安装setup.exe文件 8 使用微Pe安装过程中发现需要联网更新解决 winR 然后cmd 输入 OOBE\BYPASSNRO 9 或者关闭此进程 电脑不要插网线&#xff…

虚拟机系统iso镜像下载_如何下载正版系统镜像

许多网友想装系统&#xff0c;可是不会装(后期会推装系统的文章)&#xff0c;而且网上的系统有的装有全家桶&#xff0c;有的又有捆绑软件&#xff0c;严重的还有病毒。那么&#xff0c;在哪里去下载正版&#xff0c;安全的系统镜像呢 "MSDN&#xff0c;我告诉你"这个…

自定义IOS系统弹框

写在之前 系统的弹框虽然很好看&#xff0c;但是有时候无法完全满足UI设计的需求&#xff0c;比如说中间要显示一个输入框&#xff0c;或者要放置一张图片&#xff0c;这里介绍一个很早之前的自定义弹框库&#xff0c;这个自定义弹框sdk写的很细致&#xff0c;定制性很强&…

华为vivo小米魅族oppo荣耀安卓手机刷IOS苹果系统

现在的刷机教程层出不穷&#xff0c;但是没有哪个大神可以开发出一款安卓刷IOS的工具&#xff0c;使得安卓手机或者安卓平板变成IOS苹果系统&#xff0c;这个工具除了可以刷安卓手机也可以刷安卓平板&#xff0c;网友实测&#xff0c;如果设备不支持的话软件会提醒不支持&#…

android高仿ios11系统,安卓仿ios11桌面全套文件

安卓手机想要苹果11的桌面的话&#xff0c;安卓仿ios11桌面全套文件app就可以帮您实现哦。安卓仿ios11桌面全套文件app是一款超级优质的手机桌面工具软件&#xff0c;平台上面拥有海量的壁纸资源&#xff0c;大家可以任意选取哦。 应用说明 修改负一屏效果 修改dock栏模糊度 修…

软件配置 | ios系统Clion下载、安装、配置环境

软件配置 软件配置 | ios系统Clion下载、安装、配置环境 软件配置下载、安装gClion 调试参考链接下载、安装调试 本文总结ios系统下Clion下载、安装、配置环境过程和可能碰到的问题。 因为是在私人电脑&#xff08;ios&#xff09;上配置的Clion&#xff0c;我还不太熟悉自己的…

iOS 13.0 至 13.7 平刷系统教程

来自 : 某QQ群分享文件&#xff0c;整理后发布 如侵权告知删除 关于 Succession 平刷插件信息&#xff1a; 插件&#xff1a;Succession 版本&#xff1a;1.4.15 开发者&#xff1a;Samg_is_a_Ninja 系统支持&#xff1a;iOS 10 至 13.7 用途&#xff1a;平刷当前系统 补充&am…

ios添加邮件收件服务器,iOS 系统邮件的基础使用

尽管有好多人的主力邮件是 Gmail,客户端的首选也是 Gmail,但是在国内的互联网环境中不可避免的要使用到本土的一些邮件服务。对于这些轻量级的服务我选择用系统自带的邮件应用进行统一的管理。本文主要讲解在系统自带邮件中,如何使用国内常用的邮件服务:如网易邮箱、QQ 邮箱…

关闭app服务器系统,ios12系统服务哪些关掉

类型&#xff1a;ios系统工具大小&#xff1a;38.7M语言&#xff1a;中文 评分&#xff1a;10.0 标签&#xff1a; 立即下载 ios12更新后&#xff0c;很多服务功能都开启了&#xff0c;是比较费电的&#xff0c;用户可以前往设置进行关闭来省电等&#xff0c;那ios12系统服务哪…

ios android 系统占用空间,iOS 系统占用了 20G 储存空间?别担心,教你快速解决!...

如果你曾经查看过自己 iOS 设备的储存空间使用情况&#xff0c;那么你可能见到过下面这种情形&#xff1a; 你可能发现了&#xff0c;系统所占据的空间太大了&#xff0c;远超过了正常的范围&#xff0c;这是怎么回事呢&#xff1f;下面就为大家解释一下出现这类情形的几种解决…

如何关闭iOS系统自动更新?最新屏蔽升级描述文件下载

新老司机朋友们晚上好&#xff0c;隔壁老王又来和大家见面了&#xff0c;鸽了有一段时间了&#xff0c;非常抱歉~ 众所周知&#xff0c;iOS 系统一直保持着较高的更新频率&#xff0c;但并不是所有用户都喜欢将自己的设备升级到最新版本&#xff0c;尤其是一些旧型号的 iPhone…

如何下载和安装 iOS开发教程

步骤 1: 在App Uploader网站&#xff0c;点击“Windows版本”下载 。 步骤 2: 在弹出框中&#xff0c;点击“下载”。 步骤 3&#xff1a;在弹出框中&#xff0c;点击“文件夹”打开。 步骤:4&#xff1a;右键选择“解压全部文件”。 步骤5&#xff1a;在弹出框中&#xff0c…

ios系统安装包下载_iOS在后台自动升级?一招教你屏蔽iOS更新

苹果产品一直被用户喜爱的最大的特点就是因为iOS系统&#xff0c;iOS系统有着流畅的体验以及完善的生态环境&#xff0c;因此历年新款iPhone都处于畅销状态。但是随着苹果旗下产品发布的越来越多&#xff0c;比如到目前为止苹果已经发布了15款iPhone产品&#xff0c;从最早的第…

ios系统安装包下载_iOS 屏蔽系统升级,描述文件版本已复活,无需越狱,请速度下载!...

沉寂已久的老方法,利用描述文件屏蔽 iOS 系统更新,目前已经正式恢复,再也不担心会自动下载更新包啦。 以往都是安装 tvOS 系列的描述文件,绕过苹果验证更新,从而达到阻止系统升级的目的,但是自 iOS 13 开始,安装 tvOS 12 会提示已过期或者失效。 而在当时,安装 tvOS 13…