三表關係:
不使用 `
public class CustomerLossView {
// 流失表字段
private Long lossId;
private String lossReason;
private Date lossDate;
// 客戶信息表字段
private Long customerId;
private String customerName;
private String phone;
private String email;
// 訂單表字段(可能多筆,取最新/匯總/第一筆)
private Long orderId;
private Date orderDate;
private BigDecimal orderAmount;
private String orderStatus;
// getter/setter/toString
}
<select id="selectLossDetail" resultType="com.example.CustomerLossView">
SELECT
cl.id AS lossId,
cl.reason AS lossReason,
cl.loss_date AS lossDate,
ci.id AS customerId,
ci.name AS customerName,
ci.phone,
ci.email,
o.id AS orderId,
o.order_date AS orderDate,
o.amount AS orderAmount,
o.status AS orderStatus
FROM customer_loss cl
LEFT JOIN customer_info ci ON cl.customer_id = ci.id
LEFT JOIN (
-- 子查詢:每客戶取最新一筆訂單
SELECT o1.*
FROM orders o1
JOIN (
SELECT customer_id, MAX(order_date) AS max_date
FROM orders
GROUP BY customer_id
) o2 ON o1.customer_id = o2.customer_id AND o1.order_date = o2.max_date
) o ON ci.id = o.customer_id
WHERE cl.id = #{lossId}
</select>
CustomerLossView view = lossMapper.selectLossDetail(lossId);
// 直接使用 view.getCustomerName(), view.getOrderAmount() 等
| 痛點 | 說明 |
|---|---|
| 配置極其繁瑣 | 需定義 ` |
| N+1 查詢風險 | 若配置不當(如 `select` 屬性),MyBatis 會對每筆主表記錄發起額外查詢,效能災難。 |
| 結果集重複行 | 一對多關聯導致主表字段重複,需 `resultMap` 的 `id` 屬性去重,易出錯。 |
| 調試困難 | SQL 與映射分離,排查欄位不匹配、類型轉換失敗極其痛苦。 |
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 扁平 Bean + 手寫 SQL | 報表、詳情頁、扁平化展示、字段固定 | SQL 可控、效能最優、映射零配置、調試直觀 | Bean 膨脹、一對多只能取單筆/聚合 |
| 巢狀 resultMap | 真正需要物件圖(如訂單+明細行集合) | 語義豐富、支援集合映射 | 配置複雜、N+1 風險、調試難 |
| 多次查詢 + 記憶體組裝 | 邏輯極其複雜、跨庫/跨服務 | 解耦、單表查詢簡單 | 多次往返、記憶體組裝代碼多 |
對於「三表關聯、結果扁平展示」的典型報表/詳情需求,**扁平化 Bean + 精心寫的單條 SQL(含子查詢取最新訂單)是最高效、最易維護的方案**。避免過度使用 MyBatis 進階映射功能,除非業務真正需要深層物件圖。SQL 寫得好,映射自然簡單。