MyBatis Plus分页查询踩坑实录:${ew.sqlSegment}与QueryWrapper的正确配合姿势
MyBatis Plus分页查询实战:规避${ew.sqlSegment}的三大典型陷阱
最近在重构一个老项目的分页查询模块时,我连续踩了三个关于MyBatis Plus分页查询的坑。这些坑看似简单,却让团队浪费了整整两天时间排查。本文将分享这些实战经验,特别是如何正确处理${ew.sqlSegment}与QueryWrapper的配合问题。
1. 分页查询的基础配置
在Spring Boot项目中集成MyBatis Plus分页功能,首先需要配置分页插件。这个步骤看似简单,但配置不当会导致后续所有分页查询失效。
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }常见配置错误:
- 忘记添加
@Configuration注解 - 没有指定正确的数据库类型(DbType)
- 在多个数据源环境下未正确配置分页拦截器
2. ${ew.sqlSegment}的三种典型误用场景
2.1 XML中的条件拼接问题
最常见的错误是在XML映射文件中错误地使用${ew.sqlSegment}。下面是一个典型的错误示例:
<select id="selectPage" resultType="com.example.entity.User"> SELECT * FROM user WHERE 1=1 ${ew.sqlSegment} </select>这种写法会导致SQL注入风险,且当QueryWrapper为空时会产生语法错误。正确的做法应该是:
<select id="selectPage" resultType="com.example.entity.User"> SELECT * FROM user <where> ${ew.sqlSegment} </where> </select>2.2 排序条件丢失问题
很多开发者发现分页查询时排序条件不生效,这是因为没有正确处理排序参数的传递。服务层应该这样构建QueryWrapper:
public IPage<User> queryUsers(UserQuery query, Pageable pageable) { QueryWrapper<User> wrapper = new QueryWrapper<>(); // 条件构建 if (StringUtils.isNotBlank(query.getName())) { wrapper.like("name", query.getName()); } // 排序处理 if (pageable.getSort().isSorted()) { pageable.getSort().forEach(order -> { wrapper.orderBy(true, order.isAscending(), order.getProperty()); }); } return userMapper.selectPage(new Page<>(pageable.getPageNumber(), pageable.getPageSize()), wrapper); }2.3 分页总数计算异常
当使用自定义SQL配合${ew.sqlSegment}时,可能会遇到分页总数计算不正确的问题。这是因为MyBatis Plus的自动分页优化在某些场景下会失效。解决方案是:
@Select("SELECT * FROM user ${ew.customSqlSegment}") IPage<User> selectUserPage(IPage<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);关键区别:
${ew.sqlSegment}包含WHERE关键字${ew.customSqlSegment}不包含WHERE关键字
3. 性能优化与最佳实践
3.1 避免N+1查询问题
在分页查询中关联其他表时,要特别注意避免N+1查询问题。MyBatis Plus提供了@TableField注解来处理简单关联:
public class User { @TableField(exist = false) private List<Role> roles; }对于复杂关联,建议使用JOIN查询:
<select id="selectUserWithRoles" resultMap="userWithRoles"> SELECT u.*, r.id as role_id, r.name as role_name FROM user u LEFT JOIN user_role ur ON u.id = ur.user_id LEFT JOIN role r ON ur.role_id = r.id <where> ${ew.sqlSegment} </where> </select>3.2 分页查询的缓存策略
对于高频访问的分页接口,合理的缓存策略可以显著提升性能:
@Cacheable(value = "userPage", key = "#query.toString() + '-' + #pageable.pageNumber + '-' + #pageable.pageSize") public Page<User> getUsers(UserQuery query, Pageable pageable) { // 分页查询逻辑 }缓存注意事项:
- 确保查询条件实现了正确的toString()方法
- 对于实时性要求高的数据,设置较短的缓存时间
- 在数据修改时及时清除相关缓存
4. 复杂查询场景解决方案
4.1 多表联合分页查询
当需要跨多表进行分页查询时,直接使用MyBatis Plus的分页功能可能会遇到问题。这时可以采用子查询方案:
<select id="selectComplexPage" resultType="com.example.vo.UserVO"> SELECT u.*, d.department_name FROM user u JOIN ( SELECT id FROM user <where> ${ew.sqlSegment} </where> LIMIT #{page.offset}, #{page.size} ) tmp ON u.id = tmp.id JOIN department d ON u.department_id = d.id </select>4.2 动态字段查询
对于需要动态选择查询字段的场景,可以结合<script>标签实现:
<select id="selectWithDynamicFields" resultType="map"> <script> SELECT <foreach collection="fields" item="field" separator=","> ${field} </foreach> FROM user <where> ${ew.sqlSegment} </where> </script> </select>对应的Java接口:
IPage<Map<String, Object>> selectWithDynamicFields( IPage<?> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper, @Param("fields") List<String> fields );在实际项目中,分页查询的复杂性往往超出预期。经过多次迭代,我们总结出一套相对稳定的分页查询工具类,能够处理90%以上的业务场景。
