Mybatis-Plus实战:用QueryWrapper和UpdateWrapper重构你项目里的老旧DAO层代码

Mybatis-Plus实战:用QueryWrapper和UpdateWrapper重构你项目里的老旧DAO层代码 MyBatis-Plus重构实战用现代条件构造器替换传统DAO层代码在维护历史Java项目时许多开发者都会遇到这样一个痛点DAO层充斥着大量字符串拼接的SQL语句或臃肿的XML配置。这些代码不仅难以维护还容易引入SQL注入风险。本文将带你用MyBatis-Plus的条件构造器QueryWrapper和UpdateWrapper对这些代码坏味道进行现代化改造。1. 识别需要重构的代码模式在开始重构前我们需要先识别哪些代码模式适合用Wrapper替换。以下是几种典型的坏味道// 反例1字符串拼接SQL public ListUser findUsers(String name, Integer minAge) { String sql SELECT * FROM user WHERE 11; if (name ! null) { sql AND name name ; // SQL注入风险! } if (minAge ! null) { sql AND age minAge; } return jdbcTemplate.query(sql, new UserRowMapper()); } // 反例2臃肿的XML配置 !-- userMapper.xml -- select idfindByCondition resultTypeUser SELECT * FROM user where if testname ! nullAND name #{name}/if if testminAge ! nullAND age #{minAge}/if /where /select这些模式的主要问题包括安全性风险字符串拼接容易导致SQL注入可读性差条件逻辑分散在字符串或XML中维护困难修改需求时需要同时改动Java代码和XML类型不安全列名和值都是字符串编译时无法检查错误2. QueryWrapper基础重构策略QueryWrapper是MyBatis-Plus提供的查询条件构造器可以类型安全地构建查询条件。我们先看如何替换上述反例public ListUser findUsers(String name, Integer minAge) { QueryWrapperUser wrapper new QueryWrapper(); if (name ! null) { wrapper.eq(name, name); // 自动处理参数绑定防止SQL注入 } if (minAge ! null) { wrapper.ge(age, minAge); // ge greater or equal } return userMapper.selectList(wrapper); }这种重构带来了以下改进类型安全方法参数直接对应Java类型防止SQL注入自动使用预编译语句代码更紧凑条件逻辑集中在同一方法内更好的IDE支持代码补全和导航更便捷2.1 复杂条件处理对于更复杂的查询条件QueryWrapper提供了强大的链式APIpublic ListUser findComplexUsers(UserQuery query) { return userMapper.selectList(new QueryWrapperUser() .eq(query.getStatus() ! null, status, query.getStatus()) .gt(query.getCreateTimeStart() ! null, create_time, query.getCreateTimeStart()) .lt(query.getCreateTimeEnd() ! null, create_time, query.getCreateTimeEnd()) .and(qw - qw .like(name, query.getKeyword()) .or() .like(email, query.getKeyword()) ) .orderByAsc(create_time) .last(LIMIT 100)); }注意虽然可以使用last()方法添加原生SQL片段但应谨慎使用以避免SQL注入3. UpdateWrapper的高级应用UpdateWrapper不仅支持条件构造还能指定更新字段是替代传统更新语句的理想选择public int updateUserStatus(Long userId, Integer newStatus) { return userMapper.update(null, new UpdateWrapperUser() .set(status, newStatus) .set(update_time, LocalDateTime.now()) .eq(id, userId)); }相比传统方式有以下优势避免实体类污染不需要先查询再更新原子性操作单条SQL完成更新动态字段更新只更新需要的字段3.1 条件更新模式UpdateWrapper特别适合实现条件更新public int incrementLoginCount(Long userId) { return userMapper.update(null, new UpdateWrapperUser() .setSql(login_count login_count 1) // 使用SQL表达式 .eq(id, userId)); }4. Lambda表达式的类型安全重构MyBatis-Plus的LambdaWrapper提供了完全类型安全的条件构造方式public ListUser findUsersLambda(String name, Integer minAge) { return userMapper.selectList(new LambdaQueryWrapperUser() .eq(StringUtils.isNotBlank(name), User::getName, name) .ge(minAge ! null, User::getAge, minAge)); }Lambda方式的优势编译时检查列名通过方法引用指定重构友好字段重命名会自动更新更清晰的代码直接关联实体属性4.1 复杂Lambda表达式对于复杂查询Lambda方式同样表现优秀public ListUser findActiveAdmins(String department) { return userMapper.selectList(new LambdaQueryWrapperUser() .eq(User::getIsActive, true) .eq(User::getRole, ADMIN) .nested(qw - qw .eq(StringUtils.isNotBlank(department), User::getDepartment, department) .or() .isNull(User::getDepartment) )); }5. 重构过程中的注意事项在实际重构过程中需要注意以下几点渐进式重构不要一次性重写所有DAO方法而应该从简单查询开始确保测试覆盖逐步推进到复杂查询测试策略Test void testFindUsers() { // 准备测试数据 userMapper.insert(new User(1L, test, 25)); // 执行测试 ListUser users userDao.findUsers(test, 20); // 验证结果 assertEquals(1, users.size()); assertEquals(test, users.get(0).getName()); }性能考虑对于简单查询Wrapper性能与原生SQL相当复杂查询可能需要评估生成的SQL效率必要时可以使用SqlParser注解配合动态SQL团队协作建立Wrapper使用规范统一Lambda表达式和字符串列名风格在代码审查中检查Wrapper使用方式6. 处理特殊场景虽然Wrapper能覆盖大部分场景但某些特殊情况需要特别处理6.1 联表查询Wrapper本身不直接支持联表但可以结合自定义SQL实现Select(SELECT u.*, d.name as dept_name FROM user u LEFT JOIN department d ON u.dept_id d.id ${ew.customSqlSegment}) ListUserWithDepartment selectUsersWithDepartment(Param(Constants.WRAPPER) QueryWrapperUser wrapper);6.2 动态表名对于分表场景可以使用动态表名处理器public class MyTableNameHandler implements TableNameHandler { Override public String dynamicTableName(String sql, String tableName) { return tableName _ getYearMonthSuffix(); } }7. 架构层面的优化在大型项目中可以进一步优化Wrapper的使用构建工具类封装常用条件public class WrapperHelper { public static T QueryWrapperT activeRecords(ClassT entityClass) { return new QueryWrapperT().eq(is_active, true); } }自定义基础Mapperpublic interface BaseRepositoryT extends BaseMapperT { default ListT findByWrapper(ConsumerQueryWrapperT consumer) { QueryWrapperT wrapper new QueryWrapper(); consumer.accept(wrapper); return selectList(wrapper); } }与Spring Data风格整合public interface UserRepository { ListUser findAll(QueryWrapperUser wrapper); default ListUser findActiveUsers() { return findAll(new QueryWrapperUser().eq(is_active, true)); } }在实际项目中我们通过这种重构将DAO层代码量减少了40%同时显著提高了代码的可读性和安全性。特别是在复杂业务逻辑中Wrapper的链式API使得条件构建更加直观和易于维护。