MyBatis 动态表查询:Map 返回值、空字段与表名白名单
动态表查询使用 Map 承接结果,区分表名替换与参数绑定,并给出空字段处理、字段选择和白名单校验的示例。
问题:查询结果结构随表变化
原需求是根据数据库表名查询数据。对于列集合不固定的结果,List<Map<String, Object>> 比一个缺少明确字段定义的 Object 返回值更容易表达真实结构。
这里改变的是结果的承接方式,并不是在运行时把一个映射语句的 resultType 改成任意实体类。若业务只涉及少量固定表,优先考虑分别定义 Mapper 与 DTO,使权限和字段范围更清楚。
表名和查询值必须分开处理
MyBatis 的 #{value} 用于参数绑定;${tableName} 会把内容直接放进 SQL。数据库表名不能当作普通值参数绑定,因此必须先由服务端将业务标识映射到固定表名。不能把请求参数直接传给 ${tableName}。
下面使用虚构的两个具有相同公开字段的示例表;实际表结构与权限规则需自行提供。
public String resolveTable(String resource) {
switch (resource) {
case "active": return "demo_customer_active";
case "archived": return "demo_customer_archived";
default: throw new IllegalArgumentException("Unsupported resource");
}
}
<select id="listPublicRows" resultType="map">
SELECT id, display_name
FROM ${tableName}
WHERE id > #{afterId}
ORDER BY id
LIMIT #{limit}
</select>
List<Map<String, Object>> listPublicRows(
@Param("tableName") String tableName,
@Param("afterId") long afterId,
@Param("limit") int limit);
调用前应完成业务权限检查,并将 limit 限制在合理范围。示例主动选择公开字段,没有继续使用用户表的 SELECT *,避免把密码、手机号等无关字段带入返回结果。
Map 中没有 NULL 字段,先核对映射配置
原记录通过 JSON 序列化再反序列化为实体,补出缺失字段。这个办法把数据库结果、命名转换与 JSON 行为耦合在了一起,容易产生类型和空值语义差异。
MyBatis 提供 callSettersOnNulls 设置,它会影响 NULL 值对应的 setter 调用或 Map 写入。若要求 Map 保留空值键,可在测试配置中验证:
<settings>
<setting name="callSettersOnNulls" value="true"/>
</settings>
这是配置层面的选择,不能仅为一个接口就不加评估地改变整个应用。若接口返回结构固定,显式 DTO 映射往往更容易说明哪些字段必有、哪些字段可空。
不再通过表名猜实体类型
原写法仅把首字母转为大写,无法把 customer_profile 完整转换为 CustomerProfile,也不能证明数据库字段和 Java 属性天然一致。修订稿移除了依赖旧版 Fastjson 和 Class.forName 猜测类名的路线。
需要转换时,应提供受控的映射关系或明确的 resultMap。将映射规则写成可检查的配置,比让接口运行时猜测类型更容易维护。
验证设计
| 输入或数据 | 应观察的结果 |
|---|---|
| 允许的业务标识 | 只查询映射中的固定表 |
| 未知标识或 SQL 片段 | 在进入 Mapper 前被拒绝 |
| 数据库列为 NULL | 根据约定保留空键或在 DTO 中表达空值 |
| 大于上限的分页数量 | 被拒绝或按明确规则限制 |
| 表中包含非公开列 | 返回结果仍只有明确选择的公开字段 |
此表是待执行的验证方案,不是已经在原项目数据库上运行的测试报告。
参考
- 本文标签: 其他
- 本文链接: https://www.tianyajuanke.top/article/7
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权