多表联查常见问题:JOIN 条件误写入 WHERE 导致左连接失效;DTO 应优先于 Map 避免运行时异常;MyBatis 中 #{} 与 ${} 使用需谨慎防注入和空值;JDBC 手写需用别名+列名取值防重名。

多表联查时 JOIN 写法不对,查出来全是 null?
不是字段没值,是 ON 条件漏了关联字段或用了 WHERE 过滤外键导致左表被过滤成内连接效果。比如查员工+部门信息,用 LEFT JOIN 却在 WHERE 里写了 dept.id = ?,结果没部门的员工直接消失。
- 把所有关联过滤条件都写进
ON子句,WHERE只留真正要筛结果的条件(如员工姓名模糊匹配) - 优先用
LEFT JOIN而非INNER JOIN,除非业务明确要求“必须有对应部门” - MySQL 8.0+ 支持
USING (dept_id)简写,但得确保两表字段名完全一致,否则还是老实用ON emp.dept_id = dept.id
DAO 层该返回 Map 还是自定义 DTO?
返回 Map<String, Object> 看似省事,但字段名拼错、类型转换异常、IDE 无提示——上线后才发现 map.get("deptName") 实际是 "dept_name",又或者 Long 被当成 Integer 报 ClassCastException。
- 一律用具体
DTO类,哪怕只比实体多一两个字段,也单独建EmployeeWithDeptDTO - MyBatis 的
<resultMap>或 JPA 的@SqlResultSetMapping必须显式映射列名到属性,别依赖下划线转驼峰自动识别 - 如果 DTO 字段和数据库列名不一致,MyBatis 里用
column="dept_name"+property="deptName"配对写死,不靠配置开关
MyBatis 多表查询的 #{} 和 ${} 哪里不能乱用?
${} 是字符串拼接,用在 ORDER BY 动态字段或 IN 列表时容易被 SQL 注入;而 #{} 在 JOIN 条件里如果传的是空字符串或 null,可能生成 ON emp.dept_id = '',查不到数据还难定位。
-
ORDER BY ${sortField}必须配合白名单校验,比如if (!List.of("name", "age", "create_time").contains(sortField)) throw new IllegalArgumentException(); -
IN场景用<foreach>标签,别手拼${ids},防止注入和语法错误 -
#{deptId}传参前确保不为null或空字符串,DAO 方法加@Param("deptId") Long deptId并在 service 层判空
JDBC 手写 PreparedStatement 多表查询要注意什么?
不用框架时,ResultSet 取值最常踩的坑是:字段重名(比如 id 在员工表和部门表都存在),用 rs.getLong("id") 永远取到第一个;或者列顺序和 SQL 不一致,改了 SQL 却忘了改 rs.getString(2) 的下标。
立即学习“Java免费学习笔记(深入)”;
- SELECT 必须用别名:
SELECT emp.id AS emp_id, dept.id AS dept_id,然后统一用rs.getLong("emp_id") - 别依赖列序号,全用列名取值;如果必须用序号,注释里写明对应字段,例如
// 2: emp.name - 注意
ResultSet类型,默认是TYPE_FORWARD_ONLY,不能倒着遍历;需要多次读取就设成TYPE_SCROLL_INSENSITIVE
多表联查本身不复杂,麻烦的是字段来源模糊、空值处理不一致、还有 DAO 层和 SQL 层责任边界不清——比如该在 SQL 里 COALESCE(dept.name, '未知') 还是让 Java 判空。这种细节不提前对齐,后面加个导出功能就能卡住三天。










