MyBatisPlus条件构造器实战:从登录验证(eq查询)到用户数据统计(聚合/分组)一条龙

张开发
2026/4/21 18:42:22 15 分钟阅读

分享文章

MyBatisPlus条件构造器实战:从登录验证(eq查询)到用户数据统计(聚合/分组)一条龙
MyBatisPlus条件构造器实战从登录验证到用户数据统计全流程解析在当今企业级应用开发中数据持久层框架的选择直接影响着开发效率和系统性能。MyBatisPlus作为MyBatis的增强工具其条件构造器功能让复杂查询变得简单直观。本文将以一个用户管理后台的实际开发场景为例带你全面掌握从基础等值查询到高级聚合统计的全套解决方案。1. 项目环境搭建与基础配置在开始具体功能实现前我们需要确保开发环境正确配置。假设我们正在开发一个基于Spring Boot的用户管理系统主要涉及用户信息的CRUD操作和统计分析。首先在pom.xml中引入必要依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency用户实体类User的基本定义如下Data TableName(sys_user) public class User { TableId(type IdType.AUTO) private Long id; private String username; private String password; private Integer age; private String phone; private LocalDateTime createTime; }对应的Mapper接口只需继承MyBatisPlus的BaseMapperpublic interface UserMapper extends BaseMapperUser { }提示确保在启动类上添加MapperScan注解扫描Mapper接口避免手动逐个声明。2. 用户登录验证等值查询实战用户登录是系统最基本的功能之一需要验证用户名和密码是否匹配。这正是等值查询(eq)的典型应用场景。2.1 基础等值查询实现使用LambdaQueryWrapper可以避免硬编码字段名提升代码可维护性Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; public User login(String username, String password) { LambdaQueryWrapperUser queryWrapper new LambdaQueryWrapper(); queryWrapper.eq(User::getUsername, username) .eq(User::getPassword, password); return userMapper.selectOne(queryWrapper); } }这段代码对应的SQL是SELECT * FROM sys_user WHERE username ? AND password ?2.2 登录功能优化实践在实际项目中我们还需要考虑一些边界情况密码加密处理存储前应对密码进行加密登录失败处理区分用户不存在和密码错误并发控制防止暴力破解优化后的登录方法可能如下public LoginResult login(LoginDTO dto) { // 先查询用户是否存在 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, dto.getUsername()); User user userMapper.selectOne(wrapper); if(user null) { return LoginResult.fail(用户不存在); } // 验证密码(假设使用BCrypt加密) if(!BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return LoginResult.fail(密码错误); } // 其他登录成功逻辑... return LoginResult.success(user); }3. 用户数据概览聚合查询应用登录成功后管理后台通常需要展示用户数据的整体概览如总用户数、平均年龄等统计信息。3.1 基础聚合函数使用MyBatisPlus支持通过QueryWrapper实现各种聚合查询public MapString, Object getUserStatistics() { QueryWrapperUser wrapper new QueryWrapper(); wrapper.select(COUNT(*) as userCount, AVG(age) as avgAge, MAX(age) as maxAge, MIN(age) as minAge); return userMapper.selectMaps(wrapper).get(0); }执行结果将返回包含以下键值对的Map{ userCount: 125, avgAge: 28.5, maxAge: 65, minAge: 18 }3.2 复杂统计场景处理对于更复杂的统计需求如按条件筛选后的统计数据可以结合where条件public MapString, Object getVipUserStats() { QueryWrapperUser wrapper new QueryWrapper(); wrapper.select(COUNT(*) as vipCount, AVG(age) as avgAge) .gt(age, 18) // 年龄大于18 .isNotNull(phone); // 手机号不为空 return userMapper.selectMaps(wrapper).get(0); }注意聚合查询结果通常以MapString, Object形式返回字段别名将作为Map的key。4. 用户分布分析分组查询实战了解用户群体的分布特征对业务决策至关重要。比如按手机号前缀分析用户地域分布。4.1 基础分组查询public ListMapString, Object getUserGroupByPhonePrefix() { QueryWrapperUser wrapper new QueryWrapper(); wrapper.select(LEFT(phone, 3) as prefix, COUNT(*) as userCount) .groupBy(prefix) .orderByDesc(userCount); return userMapper.selectMaps(wrapper); }执行结果示例[ {prefix: 138, userCount: 42}, {prefix: 159, userCount: 35}, {prefix: 186, userCount: 28} ]4.2 多维度分组统计对于更复杂的分析需求可以实现多字段分组public ListMapString, Object getUserGroupByAgeRangeAndPhone() { QueryWrapperUser wrapper new QueryWrapper(); wrapper.select(CONCAT(FLOOR(age/10)*10, -, FLOOR(age/10)*109) as ageRange, LEFT(phone, 3) as prefix, COUNT(*) as userCount) .groupBy(ageRange, prefix) .orderByAsc(ageRange) .orderByDesc(userCount); return userMapper.selectMaps(wrapper); }这种查询可以帮助我们发现不同年龄段用户的手机运营商偏好分布。5. 条件构造器高级技巧与性能优化掌握了基础用法后我们需要了解一些高级技巧来应对复杂业务场景。5.1 QueryWrapper与LambdaQueryWrapper的选择两种Wrapper各有优劣下面是主要对比特性QueryWrapperLambdaQueryWrapper字段引用方式字符串硬编码方法引用类型安全无有IDE支持一般代码提示好复杂条件构建灵活稍显繁琐聚合/分组查询支持不支持选择建议简单条件查询优先使用LambdaQueryWrapper涉及聚合、分组或动态SQL时使用QueryWrapper混合使用时可以互相转换5.2 动态条件构建技巧实际业务中查询条件经常是动态的MyBatisPlus提供了灵活的条件构建方式public ListUser searchUsers(UserQuery query) { LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); // 动态添加条件 if(StringUtils.isNotBlank(query.getKeyword())) { wrapper.and(w - w.like(User::getUsername, query.getKeyword()) .or() .like(User::getPhone, query.getKeyword())); } if(query.getMinAge() ! null) { wrapper.ge(User::getAge, query.getMinAge()); } if(query.getMaxAge() ! null) { wrapper.le(User::getAge, query.getMaxAge()); } return userMapper.selectList(wrapper); }5.3 性能优化建议索引友好确保查询条件字段有适当索引避免全表扫描合理使用select()指定返回字段分页处理大数据量查询务必使用分页缓存策略考虑对统计结果进行缓存// 分页查询示例 public PageUser getUsersByPage(int current, int size) { PageUser page new Page(current, size); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.orderByDesc(User::getCreateTime); return userMapper.selectPage(page, wrapper); }6. 实际业务场景综合应用让我们通过一个完整的业务场景串联所学知识实现一个用户管理后台的数据看板。6.1 看板数据聚合public DashboardVO getUserDashboard() { DashboardVO vo new DashboardVO(); // 总用户数 vo.setTotalUsers(userMapper.selectCount(null)); // 今日新增 LambdaQueryWrapperUser todayWrapper new LambdaQueryWrapper(); todayWrapper.ge(User::getCreateTime, LocalDate.now().atStartOfDay()); vo.setTodayNewUsers(userMapper.selectCount(todayWrapper)); // 年龄分布 QueryWrapperUser ageWrapper new QueryWrapper(); ageWrapper.select(AVG(age) as avgAge, MAX(age) as maxAge); MapString, Object ageStats userMapper.selectMaps(ageWrapper).get(0); vo.setAvgAge(Double.parseDouble(ageStats.get(avgAge).toString())); vo.setMaxAge(Integer.parseInt(ageStats.get(maxAge).toString())); // 手机运营商分布 QueryWrapperUser phoneWrapper new QueryWrapper(); phoneWrapper.select(LEFT(phone, 3) as carrier, COUNT(*) as count) .groupBy(carrier) .orderByDesc(count); vo.setCarrierDistribution(userMapper.selectMaps(phoneWrapper)); return vo; }6.2 复杂报表生成对于更复杂的报表需求可以考虑使用原生SQL与MyBatisPlus结合的方式Select(SELECT DATE(create_time) as day, COUNT(*) as count FROM sys_user WHERE create_time BETWEEN #{start} AND #{end} GROUP BY DATE(create_time)) ListMapString, Object getUserGrowthTrend(Param(start) LocalDateTime start, Param(end) LocalDateTime end);这种混合使用方式既保持了简单条件的便捷性又能应对复杂SQL需求。

更多文章