
本文介绍使用 JDBI 3 的 @Nested 注解与 BeanMapper 配合,将数据库中具有外键关系的两张表(如 A 和 B)查询结果自动映射为嵌套 POJO 结构(B 持有 A 实例而非原始外键值),无需手动组装,提升代码简洁性与可维护性。
本文介绍使用 jdbi 3 的 `@nested` 注解与 `beanmapper` 配合,将数据库中具有外键关系的两张表(如 a 和 b)查询结果自动映射为嵌套 pojo 结构(b 持有 a 实例而非原始外键值),无需手动组装,提升代码简洁性与可维护性。
在基于 JDBI 的 Java 数据访问层开发中,当数据库存在一对多或一对一的外键关联(例如表 b 通过 a_id 引用表 a),我们通常希望业务对象能自然体现这种关系——即 B 类直接持有 A 类型的字段,而非仅保留原始 long aId。JDBI 3 提供了优雅的声明式解决方案:@Nested 注解 + 列别名驱动的 Bean 映射。
核心实现原理
@Nested 注解用于标记 setter 方法(也可用于 getter),指示 JDBI 将查询结果中以指定前缀命名的列自动注入到嵌套对象的对应字段中。配合 @ColumnName 显式声明列名映射,可避免因多表同名列(如 id、attribute)导致的冲突。
以下为完整实现步骤:
✅ 步骤 1:定义嵌套 POJO(含 @Nested 和 @ColumnName)
// 类 B —— 主实体,内嵌 A
public class B {
private long id;
private A a; // 嵌套对象,非外键值
private String bAttribute;
@ColumnName("b_id")
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public A getA() { return a; }
@Nested // 关键:启用嵌套映射
public void setA(A a) { this.a = a; }
@ColumnName("b_attribute")
public String getBAttribute() { return bAttribute; }
public void setBAttribute(String bAttribute) { this.bAttribute = bAttribute; }
}// 类 A —— 被嵌套实体
public class A {
private long id;
private String aAttribute;
@ColumnName("a_id")
public long getId() { return id; }
public void setId(long id) { this.id = id; }
@ColumnName("a_attribute")
public String getAAttribute() { return aAttribute; }
public void setAAttribute(String aAttribute) { this.aAttribute = aAttribute; }
}? 注意:@Nested 必须作用于 setter 方法(不能只加在字段上),且该 setter 参数类型需为待嵌套的 POJO 类(如 A)。JDBI 会自动为其创建 BeanMapper。
✅ 步骤 2:编写 SQL 查询并严格匹配列别名
SQL 必须通过 AS 为所有字段指定别名,且别名需与 @ColumnName 值完全一致,并遵循“前缀 + 下划线 + 字段名”规则(如 a_id, a_attribute),确保嵌套映射器能正确识别归属:
SELECT b.id AS b_id, b.b_attribute AS b_attribute, a.id AS a_id, a.a_attribute AS a_attribute FROM a INNER JOIN b ON a.id = b.a_id;
⚠️ 重要约束:
- 若 A 中有 id 和 aAttribute 字段,则 SQL 中必须提供 a_id 和 a_attribute 别名;
- B 的字段别名(如 b_id)不可与 A 的别名冲突;
- 所有参与映射的列必须出现在 SELECT 子句中,缺失将导致嵌套对象为 null(即使数据库有值)。
✅ 步骤 3:执行映射查询
String sql = """
SELECT b.id AS b_id, b.b_attribute AS b_attribute,
a.id AS a_id, a.a_attribute AS a_attribute
FROM a
INNER JOIN b ON a.id = b.a_id
""";
jdbi.useHandle(handle -> {
List<B> results = handle
.createStatement(sql)
.execute()
.mapToBean(B.class) // 自动识别 @Nested 并构建嵌套结构
.list();
// 每个 B 实例的 .getA() 已被填充(非 null),前提是连接数据存在
});注意事项与最佳实践
- 空值处理:若某条 b 记录对应的 a 在数据库中不存在(如外键为 NULL 或 LEFT JOIN 场景),则 B.a 将为 null —— 这是预期行为,符合关系语义。
- 性能考量:此方式为单次 JOIN 查询,相比 N+1 查询(先查 B 再逐个查 A)更高效;但需确保 JOIN 条件准确,避免笛卡尔积。
- 扩展性:支持多级嵌套(如 B → A → C),只需在 A 的字段上继续使用 @Nested。
-
替代方案对比:
- 手动 ResultSet 解析:灵活但冗长易错;
- 自定义 RowMapper:适合复杂逻辑,但失去声明式简洁性;
- @SqlQuery + @RegisterRowMapper:需额外注册,不如 @Nested 直观。
通过 @Nested,JDBI 将关系型数据的层级语义无缝转化为面向对象模型,显著降低 DAO 层胶水代码量,是构建清晰、可维护数据访问层的重要实践。










