跳至主要内容

ADR-027: JSON 欄位處理策略

ADR 編號: 027 狀態: 已接受 (Accepted) 決策日期: 2026-07-29 決策者: Development Team

Context

本 ADR 取代 ADR-002;原 ADR 保留當時的方案比較與決策歷史。

部分 Entity 需要保存小型、原子、無 SQL 內部查詢需求的結構,例如地址 快照、標籤或少量 metadata。參考實作必須同時支援 MySQL、PostgreSQL、 SQL Server、Oracle 與 H2。

資料庫原生 JSON 的型別、查詢語法、索引與 Hibernate dialect 行為不同; @Lob 也會在不同資料庫映射成不等價的 CLOB / large-object 機制。直接在 collection 上標示 JDBC LONGVARCHAR 並不會自動提供可靠的 JSON serialization。

Decision

JSON 欄位採:

@Convert(converter = AddressListConverter.class)
@Column(length = Length.LONG32)
private List<Address> addresses = new ArrayList<>();

也就是:

  1. Entity 保留具體 value object / collection 型別。
  2. 使用 AttributeConverter<T, String> 負責 JSON serialization。
  3. 使用 @Column(length = Length.LONG32) 取得 dialect-aware 的大型文字 mapping。
  4. Converter 明確定義 null / empty 語意,並對無效 JSON 或 serialization failure fail fast。

不得使用:

  • @Lob
  • @JdbcTypeCode(SqlTypes.JSON)
  • collection 上的 @JdbcTypeCode(SqlTypes.LONGVARCHAR)
  • DB native JSON 作為參考實作基線
  • 解析失敗時回傳空集合、空物件或 null

適用邊界

JSON 只適用於同時符合以下條件的資料:

  • 與 owner 一起原子更新。
  • 大小與元素數有明確上限。
  • SQL 不需查詢、排序、join、unique 或部分更新內部欄位。
  • 內部值沒有獨立 identity / lifecycle。

不符合時改用一般欄位、@Embeddable@ElementCollection 或 child Entity。

JSON value 優先使用具名 immutable type。只有 schema 本來就動態時才使用 Map<String, Object>。Schema 演進需要相容性測試;不能靠 silent fallback 吞掉舊資料錯誤。

Consequences

Positive

  • 五種目標資料庫使用同一個文字儲存契約。
  • Java 型別與 serialization 邊界明確。
  • 不依賴資料庫 large-object API 或 native JSON dialect。
  • 資料毀損會立即現形。

Trade-offs

  • DB 無法用 portable SQL 查詢 JSON 內部欄位。
  • 每種結構需要 converter 與 round-trip tests。
  • 大型或高更新頻率集合仍須正規化。

Verification

每個 converter 至少驗證:

  • null、empty 與一般值 round trip
  • Unicode 與 java.time value
  • unknown / missing field 的相容策略
  • malformed JSON 與 serialization failure 會拋錯
  • MySQL、PostgreSQL、SQL Server、Oracle、H2 的 DDL 與 round trip

完整 operational rules 見 JPA Entity 欄位型別規範

Change Log

日期變更
2025-12-22初版採文字型 JSON 儲存
2025-12-29改採 @Lob + AttributeConverter
2026-07-29移除 @Lob;收斂為 typed converter + Length.LONG32,並要求 fail fast