MyBatis-Plus插件: 分页-sql性能分析-乐观锁

📅 2026/6/28 2:49:54
MyBatis-Plus插件: 分页-sql性能分析-乐观锁
文章目录[toc]1 分页插件1.1 添加分页插件配置1.2 内置分页查询1.3 Page 对象常用方法1.4 自定义 XML 分页1.4.1 Mapper 接口定义方法1.4.2 编写 XML 映射文件1.4.3 测试自定义分页2 SQL 性能分析插件3 乐观锁插件3.1 问题场景并发修改导致数据覆盖3.2 模拟修改冲突3.2.1 建表与实体类3.2.2 测试并发修改3.3 乐观锁实现原理3.4 MyBatis-Plus 实现乐观锁3.4.1 添加 Version 注解3.4.2 添加乐观锁插件配置3.4.3 测试乐观锁效果3.5 失败重试优化1 分页插件MyBatis-Plus 内置了分页插件配置后即可使用无需引入额外依赖。1.1 添加分页插件配置创建配置类注册PaginationInnerInterceptorConfigurationpublicclassMybatisPlusConfig{BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}}DbType.MYSQL指定数据库类型插件会根据不同数据库生成对应的分页 SQLMySQL 使用LIMITOracle 使用ROWNUM等。1.2 内置分页查询使用selectPage()方法进行分页查询传入Page对象指定分页参数TestpublicvoidtestPage(){// 查询第1页每页5条PageUserpagenewPage(1,5);userMapper.selectPage(page,null);// 获取分页数据ListUserlistpage.getRecords();list.forEach(System.out::println);System.out.println(当前页page.getCurrent());System.out.println(每页显示的条数page.getSize());System.out.println(总记录数page.getTotal());System.out.println(总页数page.getPages());System.out.println(是否有上一页page.hasPrevious());System.out.println(是否有下一页page.hasNext());}selectPage()的第二个参数为条件构造器传入null表示不附加查询条件。1.3 Page 对象常用方法方法返回类型说明getRecords()ListT获取当前页的数据列表getCurrent()long获取当前页码getSize()long获取每页显示条数getTotal()long获取总记录数getPages()long获取总页数hasPrevious()boolean是否有上一页hasNext()boolean是否有下一页1.4 自定义 XML 分页内置的selectPage()只能做简单的全表分页。如果需要带自定义条件的分页查询可以在 Mapper 接口中定义方法配合 XML 编写 SQL分页逻辑由插件自动处理。1.4.1 Mapper 接口定义方法publicinterfaceUserMapperextendsBaseMapperUser{/** * 根据年龄查询用户列表分页显示 * param page 分页对象必须放在第一个参数位置传入后自动分页 * param age 年龄 */PageUserselectPageVo(Param(page)PageUserpage,Param(age)Integerage);}Page参数必须放在第一个位置插件会自动识别并拼接分页 SQL。1.4.2 编写 XML 映射文件在resources/com/xq/mapper/目录下创建UserMapper.xml?xml version1.0 encodingUTF-8 ?!DOCTYPEmapperPUBLIC-//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtdmappernamespacecom.xq.mapper.UserMapper!--SQL片段记录基础字段--sqlidBaseColumnsuid, name, age, email/sql!--PageUser selectPageVo(PageUser page, Integer age);--selectidselectPageVoresultTypecom.xq.pojo.UserSELECTincluderefidBaseColumns/includeFROM tb_user WHERE age #{age}/select/mapperXML 中只需要写业务 SQL不需要写LIMIT分页插件会自动追加。1.4.3 测试自定义分页TestpublicvoidtestSelectPageVo(){PageUserpagenewPage(1,5);userMapper.selectPageVo(page,20);ListUserlistpage.getRecords();list.forEach(System.out::println);System.out.println(当前页page.getCurrent());System.out.println(每页显示的条数page.getSize());System.out.println(总记录数page.getTotal());System.out.println(总页数page.getPages());System.out.println(是否有上一页page.hasPrevious());System.out.println(是否有下一页page.hasNext());}2 SQL 性能分析插件PerformanceInterceptor用于输出每条 SQL 的执行时间可设置最大执行时长超时则抛出异常。该插件仅用于开发环境调试不建议在生产环境使用。注意高版本的 MyBatis-Plus3.2已移除此插件。如需使用需将 MyBatis-Plus 版本降至 3.1.xdependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus/artifactIdversion3.1.1/version/dependency在 MyBatis 核心配置文件mybatis-config.xml中引入插件configurationplugins!-- SQL 执行性能分析开发环境使用线上不推荐 --plugininterceptorcom.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor!-- sql 最大执行时长毫秒超时抛异常 --propertynamemaxTimevalue100/!-- SQL 是否格式化输出默认 false --propertynameformatvaluetrue//plugin/plugins/configuration配置项说明maxTimeSQL 最大允许执行时间毫秒超时抛出异常format是否格式化输出 SQL设为true可读性更好配置后在applicationContext.xml中引入该配置文件即可生效。如果将maxTime设置过小执行 SQL 时会直接报错便于在开发阶段发现慢查询。3 乐观锁插件3.1 问题场景并发修改导致数据覆盖两个操作员同时读取了商品价格 100 元小李将价格 50写入 150 元小王将价格 -30写入 70 元由于小王的写入在小李之后小李的修改被覆盖最终价格为 70 元低于成本价 80 元。操作顺序操作人读取价格操作写入价格1小李100501502小王100-3070期望结果---120实际结果---70小李的操作被覆盖针对这种并发冲突有两种解决思路悲观锁小李操作时锁定数据小王必须等小李操作完才能修改乐观锁不加锁但在更新时检查数据是否被其他人修改过如果被修改过则更新失败3.2 模拟修改冲突3.2.1 建表与实体类CREATETABLEt_product(idBIGINT(20)NOTNULLCOMMENT主键ID,NAMEVARCHAR(30)NULLDEFAULTNULLCOMMENT商品名称,priceINT(11)DEFAULT0COMMENT价格,VERSIONINT(11)DEFAULT0COMMENT乐观锁版本号,PRIMARYKEY(id));INSERTINTOt_product(id,NAME,price)VALUES(1,外星人笔记本,100);DataNoArgsConstructorAllArgsConstructorTableName(t_product)publicclassProduct{privateLongid;privateStringname;privateIntegerprice;privateIntegerversion;}publicinterfaceProductMapperextendsBaseMapperProduct{}3.2.2 测试并发修改AutowiredProductMapperproductMapper;TestpublicvoidtestConcurrentUpdate(){// 1、小李取出商品价格Productp1productMapper.selectById(1L);System.out.println(小李取出的价格p1.getPrice());// 2、小王取出商品价格Productp2productMapper.selectById(1L);System.out.println(小王取出的价格p2.getPrice());// 3、小李将价格加了50元存入数据库p1.setPrice(p1.getPrice()50);intresult1productMapper.updateById(p1);System.out.println(小李修改结果result1);// 4、小王将商品减了30元存入数据库p2.setPrice(p2.getPrice()-30);intresult2productMapper.updateById(p2);System.out.println(小王修改结果result2);// 最终结果Productp3productMapper.selectById(1L);// 价格被覆盖结果为70System.out.println(最后的结果p3.getPrice());}未使用乐观锁时小王的修改直接覆盖了小李的修改最终价格为 70。3.3 乐观锁实现原理乐观锁通过version字段实现取出记录时获取当前version值更新时将version 1作为更新条件如果WHERE中的version与数据库中的不一致说明数据已被其他人修改更新失败影响行数为 0-- 取出记录version 1SELECTid,name,price,versionFROMt_productWHEREid1;-- 更新时携带 version 条件UPDATEt_productSETprice?,versionversion1WHEREid1ANDversion1;3.4 MyBatis-Plus 实现乐观锁3.4.1 添加 Version 注解在实体类的版本号字段上添加Version注解DataNoArgsConstructorAllArgsConstructorTableName(t_product)publicclassProduct{privateLongid;privateStringname;privateIntegerprice;Version// 标识乐观锁版本号字段privateIntegerversion;}3.4.2 添加乐观锁插件配置在配置类中注册OptimisticLockerInnerInterceptorConfigurationpublicclassMybatisPlusConfig{BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();// 分页插件interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));// 乐观锁插件interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());returninterceptor;}}3.4.3 测试乐观锁效果配置完成后再次执行并发修改测试MyBatis-Plus 生成的 SQL 会自动携带version条件步骤操作人执行 SQL结果①小李查询SELECT id,name,price,version FROM t_product WHERE id?price100, version0②小王查询SELECT id,name,price,version FROM t_product WHERE id?price100, version0③小李修改UPDATE t_product SET name?, price150, version1 WHERE id1 AND version0成功version 变为 1④小王修改UPDATE t_product SET name?, price70, version1 WHERE id1 AND version0失败version 已不是 0小王的更新因version不匹配而失败影响行数为 0最终价格为 150。3.5 失败重试优化小王修改失败后可以重新查询最新数据再次尝试更新确保两次操作都生效TestpublicvoidtestConcurrentVersionUpdate(){// 小李取数据Productp1productMapper.selectById(1L);// 小王取数据Productp2productMapper.selectById(1L);// 小李修改 50p1.setPrice(p1.getPrice()50);intresult1productMapper.updateById(p1);System.out.println(小李修改的结果result1);// 小王修改 -30p2.setPrice(p2.getPrice()-30);intresult2productMapper.updateById(p2);System.out.println(小王修改的结果result2);if(result20){// 失败重试重新获取最新数据含最新 version再次更新p2productMapper.selectById(1L);p2.setPrice(p2.getPrice()-30);result2productMapper.updateById(p2);}System.out.println(小王修改重试的结果result2);// 老板看价格Productp3productMapper.selectById(1L);// 最终结果120100 50 - 30System.out.println(老板看价格p3.getPrice());}重试后小王基于最新价格 150 进行 -30 操作最终价格为120符合预期。乐观锁适用场景读多写少的业务。如果写操作频繁乐观锁会导致大量重试此时应考虑使用悲观锁。