mybatisplus分页原理,MyBatis-Plus 使用这么方便,底层是如何处理的呢?
mybatisplus分页原理,MyBatis-Plus 使用这么方便,底层是如何处理的呢?详细介绍
本文目录一览: Mybatis分页对比MybatisPlus分页
Mybatis使用内存分页
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
???????分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
————————————————
MybatisPlus是物理分页
基本步骤是:
1 原生SQL解析
2 判断有无page分页对象。没有对象就直接进行SQL操作,有对象就继续分页解析
3 count语句优化。根据SQL条件进行count优化,这点不同于传统的 select count(1) from (你的 业务 SQL) 做下简单的封装,详细源码可以自己看,或者打印出执行的SQL可以清楚的看到
4 通过count数量和之前前端传递来的分页pageNum和pageSize对SQL进行拼接
5最终执行拼接完整的SQL实现分页处理
总结:mybatis的是内存分页,全查出来丢在内存中,这样子很不好! 现在大面积使用的插件MybatisPlus是物理分页,先查询总数(不像网上很多人说的只是简单外包一层计算count),再根据分页参数瓶装SQL然后执行分页查询
Spring Boot(十二):MyBatis-Plus的多数据源和分页
同一个项目有时会涉及到多个数据库,这时我们就要配置多个数据源。配置多数据源的常见情况有以下两种:
1)同一个项目中涉及两个或多个业务数据库,它们之间相互独立,这种情况也可以作为两个或多个项目来开发
2)两个或多个数据库之间是主从关系,主库负责写,从库负责读
1、pom.xml配置
在pom.xml中增加MyBatis-Plus多数据源依赖:
2、配置文件配置
在配置文件application.yml中配置我们需要连接的数据库:blog和user,默认为blog
3、启动类配置
在@SpringBootApplication注解上增加exclude = DruidDataSourceAutoConfigure.class配置:
这个配置的作用是去掉对DruidDataSourceAutoConfigure的自动配置,否则程序会报错:
原因:
DruidDataSourceAutoConfigure在DynamicDataSourceAutoConfiguration之前,其会注入一个DataSourceWrapper,会在原生的spring.datasource下找url, username, password等,而我们动态数据源的配置路径是变化的。
4、实体类和dao层配置
在po文件夹下创建blog和user文件夹,分别用于存储blog数据库和user数据库的实体:
注解:
@TableName: 表名注解,标识实体类对应的表
@TableId: 主键注解,当type = IdType.AUTO时,表示这个主键是自增主键
在dao文件夹下创建blog和user文件夹,分别用于存储blog和user的dao:
注解:
@Repository: 将数据访问层(DAO层)的类标识为Spring Bean
@DS: 配置非默认数据源,本示例中blog为默认数据源,user为非默认数据源,在使用@DS注解时,有如下注意事项:
1)不能使用事务,否则数据源不会切换,使用的还是第一次加载的数据源
2)第一次加载数据源之后,第二次,第三次……操作其他数据源,如果数据源不存在,使用的还是第一次加载的数据源
3)数据源名称不要包含下划线,否则不能切换
5、测试验证
编写ArticleController和UserInfoController:
注 : 业务逻辑复杂时,Controller和Mapper中间会有Service层来处理业务逻辑,现在我们就简单的测试一下多数据源,所以直接使用Controller调用Mapper了
1、配置分页插件
2、分页方法
1)使用MyBatis-Plus的selectPage方法
使用MyBatis-Plus的selectPage方法,返回了IPage,示例:
2)sql分页
有时候有些分页需要关联多张表,使用LambdaQueryWrapper不太方便,这时候可以自己写sql来实现分页,主要有两种:纯sql自己实现分页和使用IPage实现分页
注 : 这里的sql示例就使用单表查询了,具体的可根据业务场景使用多表查询
A、纯sql自己实现分页
分页的数据list和总条数单独调用方法返回 :
B、使用IPage实现分页(常用)
返回IPage,返回值的数据结构见“ 1)使用MyBatis-Plus的selectPage方法 ”
本文简单介绍了一下MyBatis-Plus的多数据源和分页,本文示例代码, 详见https://gitee.com/tunan222/spring-boot-demo
若您觉得还可以,请帮忙点个 “赞” ,谢谢
MyBatisPlus 分页插件和数据库行锁的几点思考
前段时间跟踪 MyBatis 源码,分析 MyBatis 的分页查询结果后,发现传入的 IPage 参数结果已经包含了查询数据了,以为分页查询语句的关键在于第一个入参必须是 IPage ,而不需要返回值了呢。
昨天发现不是这么回事儿,本文再回顾一下 MyBatis 分页插件的用法及三个发现:
本文讲解答上面三个问题。
第一步 ,设置分页查询插件。
第二步 ,编写分页查询 DAO 方法:
该方法执行完成后,查询数据会存储到 iPage 参数中,可以直接获取方法返回值。值得注意的是,这个方法必须有返回值。
我最初以为,查询结果都存储到参数中了,是不是方法定义中可以不用返回值了。昨天编码时就随手写成这样了:
结果,执行报 了 SQL 异常:
纳闷了半天,这分页查询怎么就变成了单条查询了呢?对比旧项目代码,还原分页查询方法,正常了。
结论 :MyBatisPlus 分页方法返回值必须是 IPage ,不能为 void 。
以往页面的分页查询,每页数据都很少,没有发现这个问题。
这次实现的是一个批处理任务,一次处理的数据要尽量大。 iPage 分页参数 size 初始设置为 1000,发现日志输出的记录数总是 500 条 ,分页参数失效了,为何呢?
使用客户端连接数据库查询,一次能取 1000 条,而 MyBatisPlus 分页查询,这个 500 条是谁控制的呢?能否修改呢?
答案是 : PaginationInterceptor 限制了单页条数 500,如果需要,可以这样修改:
业务要求某个任务设计成多机、并行任务,且要保证数据一致。Quartz 框架的分布式任务只能保证任务被一个节点执行,不符合需求,Spring Task 倒是可以实现。
所以问题就变成分布式锁的设计了,参考 Quartz 的集群方案中的锁机制,实现基于数据库行锁的锁。
没测试基于行锁的分布式锁之前,我以为某个事务执行 select for update 之后,其他事务再对相同记录执行该操作的话,应该会报异常,导致锁获取失败。
测试发现,某条记录被锁定之后,交互流程大概是这样的:
结论 :数据库记录的行锁是排他的,其他事务会阻塞等待。
辛丑年腊月二十八,上述就是今年最后一个工作日的总结。
收拾收拾,准备迎接农历新年!
MyBatis-Plus 使用这么方便,底层是如何处理的呢?
MyBatis-plus是完全基于MyBatis开发的一个增强工具,是在MyBatis的基础上做增强的框架,为简化开发、提高效率而生。
它在MyBatis原本的框架上增加了很多实用性功能,比如乐观锁插件、字段自动填充功能、分页插件、条件构造器、sql 注入器等等。使用 MyBatis-plus 可以完全不写任何 XML 文件,直接使用继承了BaseMapper 接口的类对象完成对数据库的映射操作
基于映射的原理,MyBatis-plus 必然要实现 Mapper中的方法与 SQL 语句的对应转化,以下即为 MyBatis-plus 重要流程图例
1.在 MyBatis-plus 中, MybatisPlusAutoConfiguration 自动配置类的 sqlSessionFactory() 方法为 Spring提供创建 sqlSession 的工厂类对象,对 sqlSessionFactory 进行定义的定义类变为了 MybatisSqlSessionFactoryBean 。
在 sqlSessionFactory() 方法中,除了注入 MyBatis本身的组件,还会注入MyBatis-plus 的 主键生成器、SQL 注入器等组件,最后通过 MybatisSqlSessionFactoryBean#getObject() 方法获取到 sqlSessionFactory 对象
2. MybatisSqlSessionFactoryBean#getObject() 执行懒加载策略,最后通过 buildSqlSessionFactory() 方法创建 SqlSessionFactory 工厂类对象。这个方法的流程很长,不过大致可以分为两个步骤:
3. MybatisXMLConfigBuilder#parse() 会去解析配置文件,最后会调用到其内部方法 mapperElement() 。这个方法完成解析 Mapper工作,并将其添加到配置类 MybatisConfiguration 中
4. MybatisConfiguration#addMapper() 方法其实是去调用 MybatisMapperRegistry#addMapper() 方法,其核心是 MybatisMapperAnnotationBuilder#parse()
5. MybatisMapperAnnotationBuilder#parse() 方法真正开始完成 Mapper 接口中的方法与 SQL 语句的映射,其中 parseStatement() 方法是解析 @Select/@Update 等注解写入的 SQL语句,而代码 GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type ) 通过 MaBatis-plus的 SQL 注入器完成 Mapper 方法与 SQL 语句的转化
6. AbstractSqlInjector#inspectInject() 会完成 BaseMapper 接口中提供的通用方法对应的 SQL 语句准备,这部分主要通过 AbstractMethod#inject() 方法完成
7. AbstractMethod#inject() 方法并没有什么特别的操作,只是调用其子类实现 injectMappedStatement() 方法。以 SelectOne#injectMappedStatement() 为例,其 SQL 语句的核心在于 SqlMethod 类,这个枚举类中缓存了可以动态拼接的 SQL 语句脚本,只需要填上参数 format 就可以得到 SQL 语句的执行脚本。
以上过程结束,只需要将所有信息通过 addInsertMappedStatement() 方法封装成 MappedStatement 对象并将其加入到容器中,这样 Mapper接口方法调用时,就可以通过 动态代理 的方式找到其对应执行的 SQL 脚本,至此 SQL 语句准备及配置解析就完成了。
最后拼接的 SQL 语句 脚本形式如下示例,实际执行数据库操作时会解析这个脚本完成变量替换,从而得到可执行的 SQL 语句
8. SqlSessionFactory 对象的创建需要回到 MybatisSqlSessionFactoryBean#buildSqlSessionFactory() 方法中,很容易追踪到 MybatisSqlSessionFactoryBuilder#build() 方法,最后其实是通过 SqlSessionFactoryBuilder#build() 方法创建了一个 DefaultSqlSessionFactory 对象返回
1. @MapperScan 注解通过 @Import(MapperScannerRegistrar.class) 引入扫描注册的类 MapperScannerRegistrar ,该类实现了 ImportBeanDefinitionRegistrar 接口并重写 registerBeanDefinitions() 方法,在该方法中注册了 MapperScannerConfigurer 类
2. MapperScannerConfigurer 是 Mapper接口的扫描配置类,实现了 BeanDefinitionRegistryPostProcessor 接口,其 postProcessBeanDefinitionRegistry() 方法会在容器启动过程中被回调,通过 ClassPathMapperScanner#scan() 方法完成 Mapper 的扫描注册
3. ClassPathMapperScanner#processBeanDefinitions() 将扫描到的 Mapper接口生成的对应 BeanDefinition 的 beanClass 属性替换为 MapperFactoryBean ,这样每次获取 Mapper 实例实际是通过 MapperFactoryBean 的实例去获取
此处体现了 FactoryBean 的定位,即用于获取同一类 bean 的工厂 bean。
4. @Autowired 自动注入 Mapper 触发容器获取 bean 的方法,调用到 MapperFactoryBean#getObject() 方法,最终调用到 sqlSessionTemplate#getMapper() 方法
5.MyBatis-plus 使用的配置类是 MybatisConfiguration ,最终调用到 MybatisMapperRegistry#getMapper() 方法,这里就进入了动态代理获取 MapperProxy 实例的流程
6. MybatisMapperProxyFactory#newInstance() 方法给自动注入返回一个 MybatisMapperProxy 代理对象
7.调用 Mapper 接口的方法触发代理对象的 MybatisMapperProxy#invoke() ,此时根据 Mapper 对象被调用的方法生成 MybatisMapperMethod 对象,通过 MybatisMapperMethod#execute() 去真正地执行 SQL 语句,从而完成数据库操作。
MybatisPlus与前端分页工具结合实现
虽然MybatisPlus提供了PaginationInnerInterceptor插件用来分页,并且该插件使用起来也还不错,但是我们开发项目的时候总是希望前后端搭配干活,实现一些好用的功能。
比如说前端表单点击表头排序,这个功能我们就可以结合分页插件完成。而且我们现在很多时候会开发多端项目,在不同前端使用不同组件适配后端接口的时候,总可能遇到命名方式不一致的问题,比如A前端框架中当前页叫currPage,B框架中叫page等情况。基于这样的实际情况,我们需要对项目中MybatisPlus的分页进行一定程度的封装,让它能满足我们不同情况下的实际需要。
首先是分页插件的配置
配置好分页插件以后MybatisPlus就支持分页了,可以使用service的page方法或者mapper的selectPage方法进行分页。这两个方法都需要传入com.baomidou.mybatisplus.extension.plugins.pagination.Page对象,这个对象也就是实际用来分页的参数对象了。我们可以在这个对象中设置分页的页数,每页的数据数量,同时也可以设置排序的字段、排序的方式。但是排序字段是直接通过字符串连接的方式填写在sql中的,所以是存在sql注入的风险的,所以我们需要个过滤SQL注入工具类,我参考了JeecgBoot中的工具类,稍作修改
com.baomidou.mybatisplus.extension.plugins.pagination.Page这个分页工具只是提供了分页的功能,但是并不能很方便的直接拿来使用,所以我封装了一个工具类,用来通过我们写好的配置从参数中获取分页需要的信息以及配合前端Table排序的参数信息,并且过滤了SQL注入
相同的,不同的Table框架中接收的数据格式也有一些差别,我们分页方法返回的com.baomidou.mybatisplus.core.metadata.IPage也不能满足我们的格式要求。所以根据实际情况封装一个工具类,用来返回我们需要的数据格式
最后分页调用的时候就变得简单了
SpringBoot+Mybatis-Plus两种分页方法
首先配置mybatis-plus配置
第一种方式,mybatis-plus原生QueryWrapper方式分页,这种方式比较简单,可以不用修改Mapper,适合简单的增删改查。
第二种方式,使用mapper文件的select注解,优点是可以方便的建立查询语句,可以联合多表查询。
Mapper文件
Controller文件
mybatis-plus分页查询
如果只是单表,那么分页查询就容易的多了 这里的@ModelAttribute注解可以将前端传过来的 current 和 size 字段映射到Page对象中 BaseController中
关联多表分页查询 PbBuildingController中
PbBuildingService
PbBuildingMapper
PbBuildingMapper.xml
如果仅仅查列表而不需要分页,前端不需要传current和size参数,而后台不需要Page参数
如果Mybatis-Plus分页查询且不返回总数total 使用 // 不查询总记录数 page.setSearchCount(false); Mybatis-Plus分页查询不返回总数total
Mybatis-plus之分页泛型转换
对于vo和po严格规范的同学来说,在使用mybatis-plus进行分页时每次都需要复制分页信息或者重写分页api。其实mybatis-plus早已为我们解决这个问题了,细心的同学会发现在IPage中有一个convert方法,没错!就是这个方法。以后写分页就可以这样写了
其源码如下:
可知,其做了一个泛型的强制转换,同时保留了分页信息。
springboot+mybatisplus+sqlserver(2008SR)整合分页问题
1.根据官网mybatis-plus的分页插件配置,就是一个拦截器,对分页数据的拦截。可是获取分页的时候默认加载了全部。而且配置了sql日志查询,查看到没有limit或其他分页相关的信息。
解决方法:
1> 在分页插件那边要配置相关信息,
2> 正常使用mybatis-plus的crud即可。