1. MyBatis-Plus 复杂查询实战指南
在 Java 持久层框架中,MyBatis-Plus 作为 MyBatis 的增强工具,通过简洁的 API 大幅提升了开发效率。但在实际业务中,我们经常会遇到需要自定义 SQL 和构建复杂查询的场景。本文将深入探讨如何在不失 MyBatis-Plus 便捷性的前提下,灵活应对各种复杂查询需求。
2. 核心功能解析
2.1 基础查询能力扩展
MyBatis-Plus 的 QueryWrapper 提供了丰富的条件构造方法:
java复制QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "张")
.between("age", 20, 30)
.isNotNull("email");
对于需要复用查询条件的场景,建议采用链式调用+方法封装的方式:
java复制public QueryWrapper<User> buildUserQuery(String keyword, Integer minAge) {
return new QueryWrapper<User>()
.and(wrapper -> wrapper
.like("username", keyword)
.or()
.like("email", keyword))
.ge(minAge != null, "age", minAge);
}
注意:字段名建议使用字符串常量或通过 Lambda 表达式引用,避免硬编码带来的维护问题
2.2 复杂条件构建技巧
处理多表关联查询时,可以采用以下策略:
- 子查询构造:
java复制queryWrapper.inSql("dept_id", "SELECT id FROM department WHERE status = 1");
- 动态条件拼接:
java复制queryWrapper.nested(wrapper -> {
wrapper.eq("type", 1).or().eq("type", 2);
}).apply("date_format(create_time,'%Y-%m') = {0}", "2023-01");
- 函数式条件:
java复制queryWrapper.apply("LENGTH(name) > {0}", 5)
.func(condition, wrapper -> wrapper.eq("status", 1));
3. 自定义 SQL 实现方案
3.1 XML 映射文件方式
在 mapper 接口中定义方法:
java复制@Select("SELECT * FROM user ${ew.customSqlSegment}")
List<User> selectCustom(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
对应的 XML 映射文件:
xml复制<select id="selectCustom" resultType="User">
SELECT * FROM user
<where>
${ew.sqlSegment}
</where>
ORDER BY create_time DESC
</select>
3.2 注解方式动态 SQL
对于简单的动态 SQL,可以直接使用注解:
java复制@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(@Param("ew") Wrapper<User> wrapper);
对应的 Provider 类:
java复制public class UserSqlProvider {
public String selectByCondition(Map<String, Object> params) {
Wrapper<?> wrapper = (Wrapper<?>) params.get("ew");
return new SQL()
.SELECT("*")
.FROM("user")
.WHERE(wrapper.getSqlSegment())
.toString();
}
}
3.3 多表联查解决方案
对于复杂关联查询,推荐方案:
- 结果集映射:
xml复制<resultMap id="userWithDept" type="UserDTO">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<association property="department" javaType="Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
</resultMap>
<select id="selectUserWithDept" resultMap="userWithDept">
SELECT u.id as user_id, u.name as user_name,
d.id as dept_id, d.name as dept_name
FROM user u LEFT JOIN department d ON u.dept_id = d.id
${ew.customSqlSegment}
</select>
- DTO 投影查询:
java复制@Select("SELECT u.*, d.name as deptName FROM user u LEFT JOIN department d ON u.dept_id = d.id ${ew.customSqlSegment}")
List<UserDTO> selectUserWithDept(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
4. 性能优化实践
4.1 查询语句优化
- **避免 SELECT ***:
java复制queryWrapper.select("id", "name", "email");
- 合理使用索引提示:
java复制@Select("SELECT /*+ INDEX(user idx_name) */ * FROM user ${ew.customSqlSegment}")
List<User> selectWithIndexHint(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
- 批量操作优化:
java复制// 批量插入
userService.saveBatch(userList, 1000);
// 批量更新
userService.updateBatchById(userList, 500);
4.2 缓存策略应用
- 二级缓存配置:
xml复制<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>
- 局部缓存控制:
java复制@Options(useCache = false)
@Select("SELECT * FROM user ${ew.customSqlSegment}")
List<User> selectNoCache(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
5. 复杂场景解决方案
5.1 动态表名处理
实现动态表名处理器:
java复制public class DynamicTableNameParser implements ITableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
return getActualTableName(tableName);
}
}
配置拦截器:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler(new DynamicTableNameParser());
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
return interceptor;
}
5.2 分库分表支持
- 分片策略配置:
java复制public class UserShardingStrategy implements IShardingStrategy {
@Override
public String doSharding(String originalSql, String tableName, Object param) {
// 根据分片键计算实际表名
int shardValue = getShardValue(param);
return originalSql.replace(tableName, tableName + "_" + shardValue);
}
}
- 分页查询处理:
java复制@Intercepts(@Signature(type= Executor.class, method="query",
args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class ShardingPageInterceptor implements Interceptor {
// 合并各分片查询结果
}
6. 最佳实践与避坑指南
6.1 常见问题解决方案
-
SQL 注入防护:
- 始终使用参数化查询
- 对用户输入进行严格校验
- 避免直接拼接 SQL 片段
-
枚举类型处理:
java复制@EnumValue
private UserType type;
- 逻辑删除冲突:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new LogicSqlInjector());
return interceptor;
}
6.2 调试技巧
- SQL 日志输出配置:
properties复制mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
- 条件构造器调试:
java复制System.out.println(queryWrapper.getCustomSqlSegment());
System.out.println(queryWrapper.getSqlSelect());
- 参数分析工具:
java复制SqlHelper.sqlFormat(sql, params);
7. 扩展功能集成
7.1 多数据源支持
- 动态数据源配置:
java复制@DS("slave")
public List<User> selectFromSlave(Wrapper<User> wrapper) {
return userMapper.selectList(wrapper);
}
- 读写分离实现:
java复制public class MasterSlaveAutoRoutingPlugin implements Interceptor {
// 根据SQL类型自动路由
}
7.2 审计功能集成
- 自动填充配置:
java复制public class MetaObjectHandler implements com.baomidou.mybatisplus.core.handlers.MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
}
- 操作日志记录:
java复制@Bean
public AuditLogInterceptor auditLogInterceptor() {
return new AuditLogInterceptor();
}
在实际项目中,我发现合理组合 MyBatis-Plus 的条件构造器和自定义 SQL 能够显著提升开发效率。对于特别复杂的查询场景,建议采用 XML 映射文件方式维护 SQL,同时充分利用 MyBatis-Plus 提供的各种拦截器进行功能扩展。当处理超大规模数据时,可以考虑结合自定义分片策略和缓存机制来优化性能。