JdbcTemplate vs MyBatis-Plus:复杂查询场景的选择指南
在数据归集系统的 TreeSelectorServiceImpl 中,大部分查询使用 MyBatis-Plus,但有 6 处使用了原生 JdbcTemplate。本文分析这 6 种场景,总结何时应该”退回”原生 SQL,以及如何在两者之间做出合理选择。
🎧 文章导读
🎵 背景音乐
前言
MyBatis-Plus(以下简称 MBP)是 Spring Boot 项目中最常用的 ORM 框架之一。它的 QueryWrapper 和 LambdaQueryWrapper 让单表查询变得非常简洁,但在某些复杂场景下,原生 JdbcTemplate 反而是更好的选择。
本文以数据归集系统 TreeSelectorServiceImpl 中的 6 处 JdbcTemplate 使用为例,分析三类核心原因,并给出优化方向。
场景清单
先看全景——6 处 JdbcTemplate 使用的原因和涉及的 SQL 特性:
| # | 方法 | 原因 | 涉及的 SQL 特性 |
|---|---|---|---|
| 1 | batchElementValue(无时间范围) |
GROUP BY 取最新记录 | MAX(TM) + GROUP BY + 子查询 |
| 2 | getEquipmentSymbolsWithNames |
跨表 LEFT JOIN | equipmentr LEFT JOIN elementb |
| 3 | latestElementValue |
GROUP BY + 可选 LEFT JOIN | MAX(TM) + GROUP BY + 关联查询 |
| 4 | getElementData |
动态表名 + 动态列名 | 表名/列名来自枚举运行时拼接 |
| 5 | batchElementData |
同上,批量版 | 动态表名 + 动态列名 |
| 6 | getSymbolChnMap |
全量查中文名映射 | 被 1、3 调用的辅助方法 |
三类核心原因

图1:聚合查询、跨表关联、动态表名三种场景对比
原因一:聚合 + 分组
MBP 的 QueryWrapper 不支持 GROUP BY + 聚合函数 + 子查询的组合。
典型场景:取每个设备的最新一条记录。这需要子查询先按设备分组取最大时间戳,再关联回主表取完整记录:
1 | -- 典型模式:取每个设备的最新一条记录 |
这种”分组取极值”的模式在数据查询中非常常见,但 MBP 的 QueryWrapper 无法表达子查询和聚合函数的组合。
原因二:跨表关联查询
MBP 的 QueryWrapper 只支持单表查询,无法表达 LEFT JOIN。
1 | -- 典型模式:关联要素中文名 |
当需要从多张表中组合数据时,MBP 的单表限制就成了瓶颈。
原因三:动态表名/列名
MBP 的实体类映射是静态的,表名和列名在编译期确定。但有些场景需要根据运行时参数动态选择表名和列名:
1 | // 动态拼接表名和列名 |
这种”配置驱动的查询”在水文监测系统中很常见——不同类型的监测数据存储在不同的表中,表名由配置枚举决定。
分页实现
在 latestElementValue 接口基础上新增分页能力,沿用 batchElementData 已有的手动分页模式:
1 | // 两个分支都加了 COUNT + LIMIT OFFSET |
返回格式:
1 | { |
优化方向
当 JdbcTemplate 的字符串拼接变得难以维护时,可以考虑以下方案:
| 方向 | 说明 | 适用场景 |
|---|---|---|
| MyBatis XML Mapper | 用 XML 定义复杂 SQL,替代 JdbcTemplate 字符串拼接 | 场景 1、2、3 |
| 动态表名插件 | MBP 的 DynamicTableNameInnerInterceptor |
场景 4、5 |
| @Select 注解 | 在 Mapper 接口上直接写 SQL | 简单的 JOIN 查询 |
当前方案功能正确,优化优先级不高。若后续维护成本增加(如 SQL 拼接出错难排查),可考虑迁移到 XML Mapper。
选择指南

图2:何时用 MyBatis-Plus、何时用 JdbcTemplate 的决策树
总结一下何时用 MBP、何时用 JdbcTemplate:
用 MyBatis-Plus 的场景:
- 单表 CRUD
- 简单条件查询(等值、范围、LIKE)
- 分页查询(内置分页插件)
- 需要类型安全的 Lambda 查询
用 JdbcTemplate 的场景:
- 聚合查询 + 子查询(GROUP BY + 聚合函数)
- 跨表 JOIN(LEFT JOIN、INNER JOIN)
- 动态表名/列名(运行时确定)
- 复杂 SQL 需要精确控制
折中方案:
- MyBatis XML Mapper:保留 MBP 的便利性,同时支持复杂 SQL
- @Select 注解:在 Mapper 接口上直接写 SQL,适合简单的 JOIN
经验总结
- 不要为了”统一技术栈”而强行用 MBP——当 QueryWrapper 无法表达你的查询时,JdbcTemplate 是更务实的选择
- 字符串拼接 SQL 要注意注入风险——虽然 JdbcTemplate 的参数化查询已经防注入,但表名和列名的动态拼接仍需谨慎
- 分页查询要先 COUNT 再查数据——避免在 Java 层做分页,浪费数据库资源
- 复杂 SQL 要有注释——尤其是子查询和 JOIN 的逻辑,方便后续维护
本文涉及代码在 TreeSelectorServiceImpl.java 中,共 6 处 JdbcTemplate 使用。