尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MyBatis-Plus动态SQL与Wrapper实战解析

MyBatis-Plus动态SQL与Wrapper实战解析 1. MyBatis-Plus动态SQL的核心价值解析在数据库操作中动态SQL一直是解决复杂查询条件的利器。传统MyBatis虽然提供了if/choose等标签实现动态SQL但需要编写大量XML文件维护成本较高。MyBatis-Plus的Wrapper体系彻底改变了这一局面让动态SQL的编写变得前所未有的简洁高效。我最近在电商后台系统中重构商品查询模块时深刻体会到Wrapper的价值。原先需要3个不同XML映射文件处理的查询条件现在只需一个Service方法配合Lambda表达式就能完成。特别是在处理前端传入的不固定查询参数时Wrapper的表现尤为出色。2. 环境准备与基础配置2.1 版本匹配建议根据当前技术生态推荐以下版本组合Spring Boot 2.7.x MyBatis-Plus 3.5.17生产环境稳定组合Spring Boot 3.x MyBatis-Plus 3.5.17需要额外处理jakarta包冲突重要提示避免直接使用最新版组合特别是Spring Boot 3.x与MP的兼容性需要特别注意。我在实际项目中遇到过HikariCP连接池的兼容性问题。2.2 基础依赖配置Maven配置示例dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.17/version /dependency !-- 分页插件必须单独声明 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-extension/artifactId version3.5.17/version /dependency3. Wrapper体系深度解析3.1 QueryWrapper与LambdaQueryWrapper对比特性QueryWrapperLambdaQueryWrapper编写方式字符串字段名方法引用类型安全无有重构友好度低高复杂条件支持强强性能差异微秒级纳秒级实际项目中我强烈推荐使用LambdaQueryWrapper。虽然初期学习成本略高但在大型项目中它能有效避免因字段名变更引发的运行时错误。3.2 核心方法实战示例场景多条件商品查询public PageProduct searchProducts(ProductQuery query, Pageable pageable) { return lambdaQuery() .eq(query.getCategoryId() ! null, Product::getCategoryId, query.getCategoryId()) .ge(query.getMinPrice() ! null, Product::getPrice, query.getMinPrice()) .le(query.getMaxPrice() ! null, Product::getPrice, query.getMaxPrice()) .like(StringUtils.isNotBlank(query.getKeyword()), Product::getName, query.getKeyword()) .in(query.getBrandIds() ! null, Product::getBrandId, query.getBrandIds()) .orderByDesc(Product::getSales) .page(new Page(pageable.getPageNumber(), pageable.getPageSize())); }这个例子展示了几个关键技巧条件方法第一个参数使用布尔判断实现真正的动态SQL链式调用保持代码整洁方法引用确保类型安全4. 高级动态SQL技巧4.1 嵌套条件处理复杂业务场景常需要处理AND/OR嵌套逻辑wrapper.and(w - w.eq(status, 1).or().eq(status, 2)) .or(w - w.between(price, 100, 200).eq(category, 电子产品));对应的SQL输出WHERE (status 1 OR status 2) OR (price BETWEEN 100 AND 200 AND category 电子产品)4.2 动态SELECT字段控制根据场景动态选择返回字段可以显著提升性能LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); if (!needDetail) { wrapper.select(User::getId, User::getName); } else { wrapper.select(User.class, info - !info.getColumn().equals(password)); }4.3 自定义SQL片段集成对于特别复杂的条件可以结合Select注解使用Select(script SELECT * FROM product where if testew ! null${ew.customSqlSegment}/if AND status 1 /where /script) PageProduct selectByCustomWrapper(Param(ew) Wrapper wrapper, Page page);5. 性能优化与踩坑实录5.1 索引命中原则Wrapper生成的SQL要注意避免在索引列上使用函数包装wrapper.apply(DATE(create_time) 2023-01-01)模糊查询尽量右匹配wrapper.likeRight(name, 张)IN语句元素数量控制在100以内5.2 分页性能陷阱MyBatis-Plus分页默认使用COUNT查询大数据量时性能堪忧。两种优化方案方案1禁用自动countPageProduct page new Page(1, 10, false);方案2覆盖优化count语句Select(SELECT COUNT(1) FROM ( SELECT id FROM product ${ew.customSqlSegment} LIMIT 10000 ) temp) Long optimizeCount(Param(ew) Wrapper wrapper);5.3 事务中的Wrapper复用一个常见的错误是在事务中复用Wrapper实例// 错误示例 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getActive, true); userService.list(wrapper); // 第一次使用 wrapper.eq(User::getDeleted, false); // 修改条件 userService.list(wrapper); // 条件已污染正确做法是每次查询创建新Wrapper实例或使用clone()方法userService.list(wrapper.clone().eq(User::getDeleted, false));6. 与Spring Boot的深度集成6.1 自动填充的最佳实践利用MetaObjectHandler实现自动填充Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, createBy, String.class, getCurrentUser()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }踩坑提醒strictInsertFill和strictUpdateFill是3.3.0版本新增方法比旧版的setFieldValByName更安全。6.2 多数据源下的Wrapper应用在多数据源环境中Wrapper需要特别注意不同数据库方言差异Oracle的LIMIT需要特殊处理表名前缀配置要统一事务传播行为要明确配置示例mybatis-plus: global-config: db-config: oracle-keywords-escape: true # Oracle关键字转义 table-prefix: t_ # 统一前缀 schema: main # 默认schema7. 复杂业务场景解决方案7.1 动态表名处理在SAAS系统中动态表名是常见需求。两种实现方式方案1使用ITableNameHandler简单场景public class DynamicTableNameHandler implements ITableNameHandler { Override public String dynamicTableName(String sql, String tableName) { return TenantContext.getTablePrefix() tableName; } }方案2SQL注入器复杂场景public class TenantSqlInjector extends DefaultSqlInjector { Override public ListAbstractMethod getMethodList(Class? mapperClass) { ListAbstractMethod methods super.getMethodList(mapperClass); methods.add(new TenantSelectById()); return methods; } }7.2 逻辑删除的深度集成逻辑删除配置mybatis-plus: global-config: db-config: logic-delete-field: deleted # 逻辑删除字段名 logic-delete-value: 1 # 删除值 logic-not-delete-value: 0 # 未删除值特殊场景处理// 临时忽略逻辑删除条件 wrapper.apply(11).eq(deleted, 1); // 查询包含已删除数据 wrapper.ignoreLogicDelete();8. 扩展与定制化开发8.1 自定义Wrapper方法扩展LambdaQueryWrapperpublic class MyLambdaQueryWrapperT extends LambdaQueryWrapperT { public MyLambdaQueryWrapperT today(String column) { super.apply(DATE({0}) DATE(NOW()), column); return this; } public MyLambdaQueryWrapperT inRange(String column, Object... values) { if (values.length 1) { return eq(column, values[0]); } return between(column, values[0], values[1]); } }8.2 SQL注入器开发实现自定义方法public class BatchInsert extends AbstractMethod { Override public MappedStatement injectMappedStatement(...) { String sql scriptINSERT INTO %s %s VALUES %s/script; // 具体实现... } }注册到配置Bean public MySqlInjector mySqlInjector() { return new MySqlInjector(); }在项目实践中我发现合理使用Wrapper可以大幅减少DAO层代码量。但也要注意避免过度封装对于特别复杂的查询XML映射文件仍然是更合适的选择。最新版的MyBatis-Plus 3.5.x在动态SQL方面做了很多优化特别是对Lambda表达式的支持更加完善值得升级体验。
返回列表