告别手写SQL!MyBatis-Plus LambdaQueryWrapper实战:从单表EQ到多表LeftJoin的完整代码示例
告别手写SQLMyBatis-Plus LambdaQueryWrapper实战从单表EQ到多表LeftJoin的完整代码示例在Java后端开发中数据库操作一直是核心且繁琐的部分。传统的MyBatis虽然强大但需要编写大量XML或注解SQL不仅效率低下还容易出错。MyBatis-Plus的LambdaQueryWrapper彻底改变了这一局面它通过Lambda表达式实现了类型安全的查询构建让开发者告别手写SQL的时代。本文将带你从基础的单表条件查询EQ开始逐步深入到复杂的多表关联查询LeftJoin通过完整代码示例展示如何在实际项目中高效使用这些功能。1. MyBatis-Plus LambdaQueryWrapper基础1.1 为什么选择LambdaQueryWrapper在传统的MyBatis开发中我们通常需要这样写查询Select(SELECT * FROM user_info WHERE age #{age}) ListUserInfo findByAge(Param(age) Integer age);这种方式存在几个明显问题SQL字符串容易拼写错误字段名修改后需要同步修改所有相关SQL缺乏编译时类型检查复杂的动态SQL难以维护LambdaQueryWrapper通过Lambda表达式完美解决了这些问题ListUserInfo users userInfoService.lambdaQuery() .eq(UserInfo::getAge, 25) .list();核心优势类型安全编译器会检查字段引用是否正确代码可读性方法链式调用更符合Java开发习惯维护方便字段名修改只需改实体类一处IDE支持自动补全和导航功能完善1.2 基础查询方法让我们从最基础的单表查询开始// 等于查询 userInfoService.lambdaQuery() .eq(UserInfo::getUserName, 张三) .list(); // 不等于查询 userInfoService.lambdaQuery() .ne(UserInfo::getAge, 30) .list(); // 大于查询 userInfoService.lambdaQuery() .gt(UserInfo::getAge, 18) .list(); // 小于查询 userInfoService.lambdaQuery() .lt(UserInfo::getAge, 60) .list(); // 区间查询 userInfoService.lambdaQuery() .between(UserInfo::getAge, 20, 30) .list();这些方法都返回QueryWrapper实例可以继续链式调用其他方法。2. 高级单表查询技巧2.1 复杂条件组合实际项目中我们经常需要组合多个条件userInfoService.lambdaQuery() .eq(UserInfo::getDeptId, 1) .gt(UserInfo::getAge, 25) .like(UserInfo::getUserName, 张) .list();对于更复杂的逻辑可以使用and和oruserInfoService.lambdaQuery() .eq(UserInfo::getStatus, 1) .and(wrapper - wrapper .gt(UserInfo::getAge, 18) .or() .eq(UserInfo::getVipFlag, true) ) .list();2.2 字段选择与排序默认查询所有字段但我们可以选择特定字段userInfoService.lambdaQuery() .select(UserInfo::getId, UserInfo::getUserName) .eq(UserInfo::getDeptId, 1) .list();排序也非常简单userInfoService.lambdaQuery() .orderByAsc(UserInfo::getAge) .orderByDesc(UserInfo::getCreateTime) .list();2.3 分页查询结合MyBatis-Plus的分页功能PageUserInfo page new Page(1, 10); userInfoService.lambdaQuery() .eq(UserInfo::getDeptId, 1) .page(page);3. 多表关联查询实战3.1 左连接(Left Join)基础多表查询是业务系统中的常见需求。假设我们有用户表(UserInfo)和部门表(Department)需要查询用户及其部门信息ListUserDTO userList userInfoMapper.selectJoinList(UserDTO.class, new MPJLambdaWrapperUserInfo() .selectAll(UserInfo.class) .select(Department::getDeptName) .leftJoin(Department.class, Department::getId, UserInfo::getDeptId) );关键点说明selectJoinList是MyBatis-Plus提供的多表查询方法第一个参数是接收结果的DTO类selectAll选择主表所有字段select添加关联表字段leftJoin定义关联关系3.2 复杂多表查询示例更复杂的场景可能涉及多表关联和条件过滤ListUserDetailDTO result userInfoMapper.selectJoinList(UserDetailDTO.class, new MPJLambdaWrapperUserInfo() .selectAll(UserInfo.class) .select(Department::getDeptName) .selectAs(Dictionary::getValue, UserDetailDTO::getGenderText) .leftJoin(Department.class, Department::getId, UserInfo::getDeptId) .leftJoin(Dictionary.class, on - on .eq(Dictionary::getType, gender) .eq(Dictionary::getCode, UserInfo::getGender) ) .eq(UserInfo::getStatus, 1) .like(UserInfo::getUserName, 张) .in(UserInfo::getDeptId, Arrays.asList(1, 2, 3)) .orderByDesc(UserInfo::getCreateTime) );3.3 关联查询性能优化多表查询需要注意性能问题只查询需要的字段避免select *合理使用索引确保关联字段有索引分页在数据库层完成不要先查全部再内存分页适当使用DTO投影减少不必要的数据传输PageUserSimpleDTO page new Page(1, 10); userInfoMapper.selectJoinPage(page, UserSimpleDTO.class, new MPJLambdaWrapperUserInfo() .select(UserInfo::getId, UserInfo::getUserName) .select(Department::getDeptName) .leftJoin(Department.class, Department::getId, UserInfo::getDeptId) .eq(UserInfo::getStatus, 1) );4. 实际项目中的应用建议4.1 封装查询逻辑为避免重复代码建议封装常用查询public class UserQuery { public static MPJLambdaWrapperUserInfo activeUsersInDepts(ListInteger deptIds) { return new MPJLambdaWrapperUserInfo() .selectAll(UserInfo.class) .select(Department::getDeptName) .leftJoin(Department.class, Department::getId, UserInfo::getDeptId) .eq(UserInfo::getStatus, 1) .in(UserInfo::getDeptId, deptIds) .orderByAsc(UserInfo::getUserName); } } // 使用 ListUserInfo users userInfoMapper.selectJoinList(UserDTO.class, UserQuery.activeUsersInDepts(Arrays.asList(1, 2, 3)));4.2 动态查询构建对于条件不确定的查询可以构建动态查询public MPJLambdaWrapperUserInfo buildUserQuery(UserQueryParam param) { MPJLambdaWrapperUserInfo wrapper new MPJLambdaWrapperUserInfo() .selectAll(UserInfo.class); if (StringUtils.isNotBlank(param.getUserName())) { wrapper.like(UserInfo::getUserName, param.getUserName()); } if (param.getMinAge() ! null) { wrapper.ge(UserInfo::getAge, param.getMinAge()); } if (CollectionUtils.isNotEmpty(param.getDeptIds())) { wrapper.leftJoin(Department.class, Department::getId, UserInfo::getDeptId) .in(Department::getId, param.getDeptIds()); } return wrapper; }4.3 常见问题与解决方案问题1N1查询问题不当的使用可能导致N1查询。例如ListUserInfo users userInfoService.list(); users.forEach(user - { Department dept departmentService.getById(user.getDeptId()); // ... });解决方案使用多表关联查询一次性获取所有数据。问题2字段名冲突多表查询时不同表可能有相同字段名。解决方案使用selectAs设置别名.selectAs(UserInfo::getName, UserDTO::getUserName) .selectAs(Department::getName, UserDTO::getDeptName)问题3复杂条件逻辑对于特别复杂的条件逻辑可以考虑使用apply方法添加自定义SQL片段拆分多个查询然后在服务层组合使用MyBatis的原生注解方式作为补充wrapper.apply(DATE(create_time) {0}, 2023-01-01);在实际项目中我们团队已经完全转向LambdaQueryWrapper进行数据库操作。最初需要一些适应但一旦熟悉后开发效率提升了至少30%而且代码的可读性和可维护性大幅提高。特别是在需要频繁修改查询条件的敏捷开发环境中这种方式的优势更加明显。