QueryWrapper实战:从SQL到Java代码的优雅转换

张开发
2026/6/30 4:47:24 15 分钟阅读
QueryWrapper实战:从SQL到Java代码的优雅转换
1. QueryWrapper基础从SQL到Java的思维转换第一次接触QueryWrapper时我盯着SQL语句看了半小时——明明一行SQL能搞定的事为什么要用Java代码重新实现直到在项目里处理第3个需求变更时我才真正体会到它的价值。想象一下当产品经理第5次调整查询条件时你只需要修改几行链式调用的Java代码而不是在XML里小心翼翼地拼接SQL字符串。QueryWrapper的本质是用面向对象的方式描述SQL查询。比如这个简单的SQLSELECT * FROM user WHERE age 18 AND name LIKE 张%用QueryWrapper实现是这样的QueryWrapperUser wrapper new QueryWrapper(); wrapper.gt(age, 18) .likeRight(name, 张);这里有两个关键点需要注意条件组合的链式调用每个条件方法都返回Wrapper对象本身形成流畅接口Fluent Interface数据库字段与Java属性的映射默认使用驼峰转下划线规则也可以通过TableField注解自定义我在实际项目中踩过的坑是当字段名包含SQL关键字时比如order、desc必须用反引号包裹wrapper.select(order, desc); // 正确 wrapper.select(order, desc); // 报错2. 一对一关联查询实战处理银行用户和银行卡的关系时我最初的做法是分别查询两个表然后手动组装对象——直到发现N1查询问题。后来改用QueryWrapper的JOIN查询性能直接提升8倍。来看这个典型场景SQL原貌SELECT u.id, u.name, c.card_number FROM user u LEFT JOIN card c ON u.id c.user_id WHERE u.id 123Java实现方案首先定义包含关联关系的实体类public class User { private Long id; private String name; TableField(exist false) // 标记非数据库字段 private Card card; } public class Card { private String cardNumber; private Long userId; // 外键字段 }然后使用QueryWrapper构建查询QueryWrapperUser wrapper new QueryWrapper(); wrapper.select(u.id, u.name, c.card_number AS cardNumber) .eq(u.id, 123) .last(LEFT JOIN card c ON u.id c.user_id); User user userService.getOne(wrapper);这里有几个实用技巧字段别名映射SQL中的c.card_number AS cardNumber会自动映射到User.card.cardNumber属性last()方法用于追加任意SQL片段但要小心SQL注入风险结果自动封装MyBatis-Plus会自动处理嵌套对象关系3. 一对多查询的三种实现方式当用户有多张银行卡时情况就变得复杂了。我经历过三种方案迭代方案一多次查询新手常用// 先查用户 User user userService.getById(123); // 再查卡片 ListCard cards cardService.list( new QueryWrapperCard().eq(user_id, user.getId()) ); user.setCards(cards);问题产生N1查询性能差方案二XML自定义SQL传统方案select idgetUserWithCards resultMapuserWithCards SELECT * FROM user u LEFT JOIN card c ON u.id c.user_id WHERE u.id #{id} /select缺点需要维护XML文件类型安全无法保证方案三QueryWrapperResultMap推荐// 实体类增加集合字段 public class User { TableField(exist false) private ListCard cards; } // 查询构建 QueryWrapperUser wrapper new QueryWrapper(); wrapper.select(u.*, c.id AS card_id, c.card_number) .eq(u.id, 123) .last(LEFT JOIN card c ON u.id c.user_id); // 需要自定义结果处理器 ListUser users userService.list(wrapper); users.forEach(user - { ListCard cards ... // 从结果集中提取卡片数据 user.setCards(cards); });关键点在于查询时获取所有关联数据在内存中完成数据组装使用MyBatis的结果处理器ResultHandler可以优化这个过程4. 多对多关系的中间表处理电商系统中的用户-商品收藏关系是典型的多对多场景。经过三个项目的迭代我总结出这套标准处理流程数据库结构user (id, name) product (id, title) user_product (user_id, product_id, create_time)Java实体设计public class User { TableField(exist false) private ListUserProduct userProducts; } public class UserProduct { private Long userId; private Long productId; private LocalDateTime createTime; TableField(exist false) private Product product; }查询构建技巧QueryWrapperUser wrapper new QueryWrapper(); wrapper.select(u.*, up.create_time, p.id AS product_id, p.title AS product_title) .eq(u.id, 123) .last(LEFT JOIN user_product up ON u.id up.user_id LEFT JOIN product p ON up.product_id p.id); ListUser users userService.list(wrapper);结果处理优化MapLong, User userMap users.stream() .collect(Collectors.toMap(User::getId, Function.identity())); users.forEach(user - { ListUserProduct ups new ArrayList(); // 解析结果集填充ups user.setUserProducts(ups); });这种方案的优点是一次查询获取所有数据内存组装效率高支持复杂的分页查询5. 动态条件构建技巧在开发后台管理系统时我经常需要处理这样的需求根据用户输入的任意条件组合查询。QueryWrapper的动态构建能力在这里大放异彩基础版QueryWrapperUser wrapper new QueryWrapper(); if (StringUtils.isNotBlank(name)) { wrapper.like(name, name); } if (startDate ! null) { wrapper.ge(create_time, startDate); }Lambda进阶版wrapper.lambda() .eq(Objects.nonNull(id), User::getId, id) .like(StringUtils.isNotBlank(name), User::getName, name) .between(Objects::nonNull, User::getCreateTime, startDate, endDate);复杂逻辑处理wrapper.and(qw - qw .gt(age, 18) .or() .isNotNull(vip_level)) .nested(qw - qw .like(address, 北京) .or() .like(address, 上海));对应生成的SQLWHERE (age 18 OR vip_level IS NOT NULL) AND (address LIKE %北京% OR address LIKE %上海%)6. 性能优化实战经验在用户量突破百万后我们遇到了严重的查询性能问题。通过以下优化手段最终将查询耗时从1200ms降到80ms索引提示wrapper.last(USE INDEX(idx_user_phone));查询字段控制// 坏实践 wrapper.select(*); // 好实践 wrapper.select(id, name, phone);批量查询优化// 原始方式产生N条SQL userIds.forEach(id - { userService.getById(id); }); // 优化方式1条SQL userService.listByIds(userIds);分页查询陷阱// 低效写法 wrapper.last(LIMIT 10000, 10); // 高效写法基于游标 wrapper.gt(id, lastMaxId) .last(LIMIT 10);这些优化手段配合数据库索引能让查询性能提升10倍以上。记得在测试环境用EXPLAIN验证执行计划我曾经因为漏加索引导致生产环境查询超时。

更多文章