news 2026/8/4 11:34:34

MyBatis-Plus自定义SQL与复杂查询实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis-Plus自定义SQL与复杂查询实战指南

1. MyBatis-Plus 自定义 SQL 与复杂查询实战指南

在持久层框架选型中,MyBatis-Plus 因其对 MyBatis 的增强特性而广受欢迎。但当业务需求超出常规 CRUD 范围时,开发者常面临两个核心问题:如何优雅地实现自定义 SQL 语句?怎样高效处理多表关联、动态条件等复杂查询场景?本文将基于真实项目经验,拆解 MyBatis-Plus 应对复杂查询的完整解决方案。

1.1 为什么需要自定义 SQL?

尽管 MyBatis-Plus 的 Wrapper 条件构造器能覆盖 80% 的查询场景,但在以下情况仍需手动编写 SQL:

  • 多表联合查询时关联字段优化
  • 需要特定数据库函数(如 Oracle 的 LISTAGG)
  • 复杂统计报表中的窗口函数应用
  • 子查询嵌套等特殊语法需求

提示:MyBatis-Plus 3.5.0+ 版本对自定义 SQL 的支持有显著增强,特别是 XML 与注解方式的混用场景。

2. 自定义 SQL 实现方案对比

2.1 XML 映射文件方式

传统 MyBatis 的 XML 配置依然是最稳定的方案。新建YourMapper.xml文件:

<!-- 示例:动态条件分页查询 --> <select id="selectComplexPage" resultType="com.example.vo.UserDeptVO"> SELECT u.*, d.dept_name FROM user u LEFT JOIN department d ON u.dept_id = d.id <where> <if test="param.deptName != null and param.deptName != ''"> AND d.dept_name LIKE CONCAT('%', #{param.deptName}, '%') </if> <if test="param.status != null"> AND u.status = #{param.status} </if> </where> ORDER BY u.create_time DESC </select>

配套的 Mapper 接口需定义对应方法:

@Mapper public interface UserMapper extends BaseMapper<User> { IPage<UserDeptVO> selectComplexPage(IPage<?> page, @Param("param") QueryParam param); }

优势

  • 支持完整的 MyBatis 动态 SQL 语法
  • 便于管理复杂 SQL 语句
  • 与 MyBatis-Plus 的分页插件天然兼容

2.2 注解方式实现

对于简单SQL,可使用@Select等注解:

@Select("SELECT * FROM user WHERE id = #{id} AND deleted = 0") User selectActiveUser(@Param("id") Long id);

注解方式限制

  • 不支持动态 SQL 标签(如<if>
  • 复杂 SQL 可读性差
  • 字符串拼接容易引发 SQL 注入风险

关键技巧:使用@SelectProvider实现动态 SQL:

public String buildComplexQuery(Map<String, Object> params) { return new SQL() {{ SELECT("*"); FROM("user"); if (params.get("name") != null) { WHERE("name = #{name}"); } }}.toString(); }

3. 复杂查询实战方案

3.1 多表关联查询优化

场景:查询用户信息及所属部门名称

方案一:结果映射(ResultMap)

<resultMap id="userDeptMap" type="com.example.vo.UserDeptVO"> <id property="id" column="user_id"/> <result property="username" column="username"/> <!-- 部门字段映射 --> <association property="dept" javaType="Dept"> <id property="id" column="dept_id"/> <result property="name" column="dept_name"/> </association> </resultMap>

方案二:DTO 投影(推荐)

@Select("SELECT u.id, u.name, d.name as deptName " + "FROM user u LEFT JOIN department d ON u.dept_id = d.id") List<UserDTO> listUsersWithDept();

性能对比

方案执行效率内存占用适用场景
ResultMap中等较高需要完整实体
DTO投影只需部分字段

3.2 动态条件构造

结合 Wrapper 和自定义 SQL 实现动态查询:

// 构建查询条件 LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(); wrapper.eq(StringUtils.isNotBlank(name), User::getName, name) .gt(age != null, User::getAge, age); // XML中引用Wrapper <select id="selectByWrapper" resultType="User"> SELECT * FROM user ${ew.customSqlSegment} </select>

注意事项

  1. customSqlSegment会自动带上 WHERE 关键字
  2. 参数前缀固定为ew.paramNameValuePairs
  3. 复杂条件建议配合<script>标签使用

4. 高级特性应用

4.1 批量操作优化

原生批量插入

<insert id="batchInsert" parameterType="java.util.List"> INSERT INTO user(name, age) VALUES <foreach collection="list" item="item" separator=","> (#{item.name}, #{item.age}) </foreach> </insert>

与 MP 批量方法对比

方法数据量事务控制性能
saveBatch<1000自动分片中等
XML批量>1000需手动
游标查询大数据流式处理最高

4.2 子查询处理

示例:查询部门人数大于平均值的部门

@Select("SELECT * FROM department WHERE id IN " + "(SELECT dept_id FROM user GROUP BY dept_id " + "HAVING COUNT(*) > (SELECT AVG(cnt) FROM " + "(SELECT COUNT(*) as cnt FROM user GROUP BY dept_id) t))") List<Department> findPopularDepts();

5. 性能调优与常见问题

5.1 SQL 注入防护

危险写法

@Select("SELECT * FROM user WHERE name = '${name}'") // 直接拼接 User findByName(@Param("name") String name);

安全写法

@Select("SELECT * FROM user WHERE name = #{name}") // 预编译 User findByName(@Param("name") String name);

5.2 慢查询优化方案

  1. 索引检查
EXPLAIN SELECT * FROM user WHERE name = 'test';
  1. N+1 问题解决
<!-- 错误方式:循环查询 --> <select id="getUsers" resultType="User"> SELECT * FROM user </select> <!-- 正确方式:一次加载 --> <select id="getUsersWithDepts" resultMap="userDeptMap"> SELECT u.*, d.* FROM user u LEFT JOIN department d ON u.dept_id = d.id </select>

5.3 事务管理要点

@Service @Transactional(rollbackFor = Exception.class) public class UserService { @Transactional(propagation = Propagation.NOT_SUPPORTED) // 只读操作 public List<User> queryComplexUsers() { // 查询方法 } }

事务传播行为选择

行为适用场景
REQUIRED(默认)多数写操作
NOT_SUPPORTED复杂查询
REQUIRES_NEW独立事务操作

6. 最新版本特性应用

MyBatis-Plus 3.5.0+ 新增功能:

  1. 动态表名支持
public String dynamicTableName(String sql, String tableName) { return sql.replaceAll("user", tableName); } // 配置 mybatis-plus: table-name-parser: com.example.config.DynamicTableNameParser
  1. SQL 注入器扩展
public class CustomSqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methods = super.getMethodList(mapperClass); methods.add(new BatchInsertMethod()); // 自定义方法 return methods; } }
  1. 多租户实现
public class TenantHandler implements TenantLineHandler { @Override public String getTenantIdColumn() { return "tenant_id"; } @Override public Expression getTenantId() { return new StringValue("当前租户ID"); } }

7. 开发实践建议

  1. 代码组织规范

    • 简单查询:使用 MP 条件构造器
    • 中等复杂度:注解方式
    • 复杂场景:XML 映射文件
  2. 监控配置

# 开启性能分析插件(开发环境) mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  1. 分页优化技巧
// 禁用 count 查询 Page<User> page = new Page<>(1, 10, false);
  1. 类型处理器扩展
@MappedTypes(Enum.class) public class CustomEnumHandler extends BaseTypeHandler<Enum> { // 实现枚举自定义存储逻辑 }

8. 复杂查询综合案例

8.1 动态多条件分页查询

查询参数

@Data public class UserQuery { private String name; private Integer minAge; private Integer maxAge; private List<Integer> deptIds; private LocalDateTime createTimeStart; private LocalDateTime createTimeEnd; }

Mapper XML

<select id="searchUsers" resultType="UserVO"> SELECT u.*, d.name AS deptName FROM user u LEFT JOIN department d ON u.dept_id = d.id <where> <if test="query.name != null and query.name != ''"> AND u.name LIKE CONCAT('%', #{query.name}, '%') </if> <if test="query.minAge != null"> AND u.age >= #{query.minAge} </if> <if test="query.maxAge != null"> AND u.age <= #{query.maxAge} </if> <if test="query.deptIds != null and !query.deptIds.isEmpty()"> AND u.dept_id IN <foreach collection="query.deptIds" item="id" open="(" separator="," close=")"> #{id} </foreach> </if> <if test="query.createTimeStart != null"> AND u.create_time >= #{query.createTimeStart} </if> <if test="query.createTimeEnd != null"> AND u.create_time <= #{query.createTimeEnd} </if> </where> ORDER BY u.create_time DESC </select>

8.2 统计报表查询

场景:按部门统计用户年龄分布

<select id="userAgeReport" resultType="map"> SELECT d.name AS deptName, COUNT(*) AS total, SUM(CASE WHEN u.age < 20 THEN 1 ELSE 0 END) AS "age<20", SUM(CASE WHEN u.age BETWEEN 20 AND 30 THEN 1 ELSE 0 END) AS "age20-30", SUM(CASE WHEN u.age > 30 THEN 1 ELSE 0 END) AS "age>30" FROM user u JOIN department d ON u.dept_id = d.id GROUP BY d.name </select>

9. 调试与问题排查

9.1 SQL 日志输出配置

logging: level: com.example.mapper: debug

9.2 常见异常处理

  1. BindingException

    • 检查 XML 中的 id 与 Mapper 接口是否一致
    • 确认 parameterType/resultType 路径正确
  2. SQLSyntaxErrorException

    • 验证 SQL 语法是否符合当前数据库类型
    • 检查保留字是否使用反引号包裹
  3. PaginationException

    • 确保分页参数正确传递
    • 检查是否配置分页插件

9.3 性能分析工具

  1. Arthas 监控 SQL
watch com.example.mapper.*Mapper '*'{params,returnObj} -x 2
  1. Druid 监控
spring: datasource: druid: stat-view-servlet: enabled: true

10. 扩展思考方向

  1. 多数据源支持

    • 动态切换数据源注解@DS("slave")
    • 读写分离配置
  2. 存储过程调用

<select id="callProcedure" statementType="CALLABLE"> {call user_procedure(#{param1,mode=IN},#{param2,mode=OUT})} </select>
  1. 自定义 TypeHandler

    • 处理 JSON 字段与 Java 对象转换
    • 实现加密字段自动加解密
  2. 逻辑删除优化

// 自定义删除语句 @Delete("UPDATE user SET deleted = 1 WHERE id = #{id}") int logicDeleteById(@Param("id") Long id);
  1. 二级缓存整合
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/4 11:32:58

3步终极指南:猫抓浏览器扩展实现网页资源智能嗅探与下载

3步终极指南&#xff1a;猫抓浏览器扩展实现网页资源智能嗅探与下载 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫抓浏览器扩展是一款强大的资…

作者头像 李华
网站建设 2026/8/4 11:32:30

SpringBoot开发虚拟宠物游戏平台实战指南

1. 项目概述&#xff1a;基于SpringBoot的线上宠物游戏平台 这个毕业设计项目是一个采用SpringBoot框架开发的线上虚拟宠物游戏平台&#xff0c;核心功能围绕"购买狗"这一主题展开。作为一个完整的电商类游戏系统&#xff0c;它融合了宠物养成、虚拟交易和社交互动等…

作者头像 李华
网站建设 2026/8/4 11:29:18

CentOS 7下载

centos7下载 百度网盘链接: https://pan.baidu.com/s/1BF84caa2P_f0fxg9W6pWkQ?pwd3t1u 提取码: 3t1u

作者头像 李华
网站建设 2026/8/4 11:29:09

飞书AI集成实战:OpenClaw部署与高级应用开发

1. 为什么要在飞书里跑AI&#xff1f; 去年我们团队接了个需求&#xff1a;每天上午10点自动收集各部门的日报数据&#xff0c;整理成可视化报表推送到高管群。最初用Python脚本定时任务实现&#xff0c;但很快发现三个痛点&#xff1a;一是非技术人员无法自助修改查询条件&am…

作者头像 李华
网站建设 2026/8/4 11:28:13

OpenClaw与Tavily集成:提升AI助手实时搜索能力

1. OpenClaw与Tavily的强强联合&#xff1a;AI助手搜索能力升级实战最近在折腾个人AI助手时&#xff0c;发现OpenClaw这个开源框架确实是个宝藏。特别是它最新支持接入Tavily搜索API的功能&#xff0c;让AI助手的知识获取能力直接上了一个台阶。作为深度使用者&#xff0c;今天…

作者头像 李华