Mapping Types and Identifiers#
JPA 透過將 Java 物件型別對應到資料庫結構來解決 Object/Relational mismatch。JPA 使用三種主要的 Object-Relational mapping 元素:type、embeddable 和 entity。Entity 與 embeddable 都透過 Hibernate Type(s) 將資料庫欄位型別與 Java 值物件關聯起來,兩者的主要差異在於 entity 必須擁有 identifier(對應資料庫 primary key),而 embeddable 則不能有自己的 identity。
不一定要對應所有資料庫欄位。有時使用一個 root entity 搭配數個 sub-entity 更實用,讓每個 business case 僅 fetch 所需資訊,同時仍享有 entity state management 的好處。
Embeddable type 透過 composition 將多個屬性組合為可重用的元件。當 entity 包含 embeddable type 時,其所有屬性會成為 owner entity 的一部分。由於 type 和 identifier 的選擇會顯著影響整體應用效能,本章深入探討這兩個主題。
Types#
JDBC 為每種支援的資料庫型別定義了 java.sql.JDBCType 列舉。Hibernate 在 JDBC 之上建構,負責 JDBC type 與 Java 對應物(primitives 或 Objects)之間的對應。

Figure 9.1: Type Mapping
Primitive Types#
Hibernate 定義了多種 primitive type 對應,涵蓋 boolean、byte、short、char、int、long、float、double 等。由於 boolean 在不同資料庫中可能以 BIT、BYTE、BOOLEAN 或 CHAR 表示,Hibernate 提供了四種 Type(s) 來解析 boolean primitive type(BooleanType、NumericBooleanType、TrueFalseType、YesNoType)。
只有 non-nullable 的資料庫欄位才能對應到 Java primitives(
boolean、byte、short、char、int、long、float、double)。對於 nullable 欄位,應使用 primitive wrapper(Boolean、Byte、Short、Char、Integer、Long、Float、Double)。
String Types#
Java String 可消耗任意大小的記憶體,而資料庫則區分有限長度(VARCHAR、NVARCHAR)與無限長度(TEXT、NTEXT、BLOB、NCLOB)的型別。Hibernate 定義了對應的 Type(s):
StringType–VARCHAR/StringStringNVarcharType–NVARCHAR/StringTextType–LONGVARCHAR/StringMaterializedClobType–CLOB/StringMaterializedNClobType–NCLOB/String
Date/Time Types#
時間處理因時區、閏秒和日光節約時間而複雜,Hibernate 提供了大量 time-related Type(s) 來涵蓋多種 Java 與資料庫表示法:
DateType/TimeType/TimestampType– 對應DATE、TIME、TIMESTAMPCalendarType/CalendarDateType/CalendarTimeType– 處理Calendar與GregorianCalendarTimeZoneType– 將TimeZone儲存為VARCHAR
建議將 timestamp 儲存為 UTC(Coordinated Universal Time),並在 data layer 進行時區轉換。這是處理時間問題的常見最佳實務。
Numeric Types#
BigIntegerType–NUMERIC/BigIntegerBigDecimalType–NUMERIC/BigDecimal
Oracle 可表示多達 38 位數字,超出 java.lang.Long(8 bytes)的範圍,此時需要使用 BigInteger 或 BigDecimal。
Binary Types#
資料庫提供多種 binary 儲存方式(RAW、VARBINARY、BYTEA、BLOB、CLOB)。Hibernate 的 binary type 對應包含:
BinaryType/MaterializedBlobType–byte[]、Byte[]BlobType/ClobType/NClobType– JDBCBlob、ClobSerializableType/SerializableToBlobType– JavaSerializable物件
UUID Types#
持久化 Java UUID 有多種方式,最有效率的是資料庫特定的 UUID column type:
UUIDBinaryType–BINARY/UUIDUUIDCharType–VARCHAR/UUIDPostgresUUIDType–OTHER/UUID
當資料庫未原生支援 UUID 時,
BINARY型別比VARCHAR需要更少的 bytes,因此相關 index 的記憶體佔用也更小。
Other Types#
Hibernate 也能對應 Java Enum(s)、Class、URL、Locale 和 Currency 等型別,均使用 VARCHAR 或其他適當的 JDBC type。
Custom Types#
開發者不限於使用內建 Type(s),可透過實作 UserType 介面來新增自訂型別。書中以 PostgreSQL 的 inet type 為例,說明如何建立自訂的 IPv4Type:
- 建立 immutable 的
IPv4wrapper class 封裝 IP 位址 - 繼承
ImmutableType基底類別,實作get()和set()方法處理型別轉換 - 使用
@Typeannotation 指示 Hibernate 使用自訂型別 - 搭配 PostgreSQL 的 GiST index(
inet_opsoperator class),可使用進階查詢功能(如子網路比對)
選擇適當的資料庫特定型別不僅能節省儲存空間,還能存取進階查詢功能。例如 PostgreSQL 的
inettype 每個 IPv4 位址只需 7 bytes,而VARCHAR(18)需要最多 18 bytes。
Identifiers#
所有資料庫表都必須有 primary key column,確保每筆資料列可被唯一識別。Primary key 可以是 natural key(具有現實世界意義)或 surrogate key(合成產生的識別碼)。
- Natural key 的唯一性由現實世界的序列產生器保證,但可能在跨國系統中產生衝突,且 compound natural key 會因多欄位 join 而影響效能
- Surrogate key 獨立於資料列內容,可由 numerical sequence 或 pseudorandom number generator(如 UUID)產生
Numerical sequence 比 UUID key 更受推薦,因為它佔用更少空間且對 index 更友善。UUID 佔 128 bits(INTEGER 的四倍、BIGINT 的兩倍),且非循序特性會造成 clustered index 的 fragmentation。
UUID Identifiers#
某些企業系統使用 UUID primary key。UUID key 可由應用程式透過 java.util.UUID 產生,或由資料庫系統指派。各資料庫的 UUID 支援情況不同:
- Oracle – 無 UUID type,使用
RAW(16)搭配SYS_GUID()函式 - SQL Server – 使用
uniqueidentifiertype,搭配NEWID()或NEWSEQUENTIALID()(pseudo-sequential,避免 fragmentation) - PostgreSQL – 原生支援
UUIDtype,但需由 data access layer 產生 identifier - MySQL – 使用
BINARY(16)搭配UUID()函式
若資料庫無內建 UUID type,
BINARY(16)column type 是首選。CHAR(32)雖可儲存 UUID 文字表示,但額外的空間開銷使其不太理想。
The Assigned Generator#
省略 @GeneratedValue annotation 時,Hibernate 使用 assigned identifier,讓 data access layer 自行控制 identifier 產生流程。此方式可將 identifier 產生的負擔從資料庫端卸載到應用程式端。
Legacy UUID Generator(uuid)#
uuid generator 產生 hex 格式的 UUID 字串,使用 8-8-4-8-4 hex digit layout,不符合 RFC 4122 標準(標準格式為 8-4-4-4-12)。
Newer UUID Generator(uuid2)#
uuid2 generator 符合 RFC 4122 標準(variant 2),支援 java.lang.UUID、byte[] 和 String 等 Domain Model 物件型別。
由於
uuid2符合 RFC 4122 且可使用BINARYcolumn type,UUIDGenerator應優先於 legacyUUIDHexGenerator。
Numerical Identifiers#
Numerical surrogate key 通常是較佳選擇,因為佔用空間少且 index 在 sequential identifier 下表現更好。JPA 定義了以下 GenerationType:
IDENTITY– 對應資料庫 identity column(auto-increment)SEQUENCE– 透過呼叫資料庫 sequence 配置 identifierTABLE– 使用獨立表格模擬 database sequence(適用於不支援 sequence 的資料庫)AUTO– 根據當前 database dialect 決定策略
Identity Generator#
Identity column(SQL:2003 標準)使用輕量級 locking 機制進行遞增,效率高。但 新指派的值只能在執行 insert statement 之後才能得知,這意味著:
- Hibernate 在
persist()呼叫時就必須發出 insert statement,打破了 transactional write-behind caching 的語意 - 使用 identity generator 的 entity 無法參與 JDBC batch updates
Sequence Generator#
Database sequence(SQL:2003 標準)相較於 identity column 有以下優勢:
- 同一個 sequence 可用於多個 column,甚至跨 table
- 值可以被 preallocated 以提升效能
- 支援 incremental steps,可搭配 application-level optimization
- Sequence call 可與 insert statement 解耦,不會停用 JDBC batch updates
Database sequence 是最有效率的 Hibernate identifier 選擇,既允許 sequence call optimizer,又不影響 JDBC batching。
Table Generator#
當資料庫不支援 sequence 時,JPA 提供 table generator 作為替代方案。使用一張資料庫表儲存最新的 sequence value,並以 row-level locking 防止併發衝突。
與 identity column 和 sequence(單次 request 即可遞增)不同,table generator 每次更新需要三個步驟:
- 執行 lock statement 確保 sequence value 不被重複配置
- 在 data access layer 遞增 current value
- 將 new value 寫回資料庫,並 commit secondary transaction 以釋放 row-level lock
Table generator 引入 serializable execution(row-level lock),在高併發環境下會嚴重影響可擴展性。由於 row-level lock 是 transactional 的(只在 transaction 結束時釋放),需要使用獨立的 database transaction 來取得 sequence value,這會對 connection pool 產生額外壓力。Identity column 和 sequence 為高併發情境高度最佳化,應是首選。
Optimizers#
Sequence 和 table generator 都支援 optimizer 策略,可減少呼叫資料庫的次數以提升 identifier 產生效能。
Hibernate 5 預設使用增強版的 SequenceStyleGenerator 和 TableGenerator(取代 legacy 的 SequenceGenerator、SequenceHiLoGenerator、MultipleHiLoPerTableGenerator),且預設啟用 hibernate.id.new_generator_mappings 為 true。
內建 optimizer 包含:
none(NoopOptimizer)– 每個 identifier 都需要一次 database roundtriphi/lo(HiLoOptimizer)– 使用 legacy hi/lo algorithm 配置 identifierpooled(PooledOptimizer)– hi/lo 的增強版,可與不知道此 optimizer 的外部系統互通pooled-lo(PooledLoOptimizer)– pooled 的變體,database sequence value 代表 lo value 而非 hi value
預設使用 pooled optimizer。若設定 hibernate.id.optimizer.pooled.prefer_lo 為 true,則預設使用 pooled-lo。
Hi/Lo Algorithm#
Hi/lo algorithm 將 sequence domain 分割為 hi groups。每個 hi value 同步取得,每個 hi group 被分配固定數量的 lo entries,可在離線狀態下配置而不會衝突:
- Hi token 由 database sequence 或 table generator 指派
- Identifier range:
Id = [n * (hi - 1) + 1, n * hi](n 為 increment size) - 當所有 lo values 用完,取得新的 hi value,循環繼續

Figure 9.2: The hi/lo algorithm
Hi/lo optimizer 的主要限制是:由於 database sequence 只指派 group values,所有資料庫客戶端都必須知道此 algorithm。DBA 手動插入資料時也必須遵循 hi/lo 規則。因此 Hibernate 提供了 pooled 和 pooled-lo 等與外部客戶端互通的 optimizer。
Pooled Optimizer#
Pooled optimizer 將 database sequence value 編碼到 identifier range 的邊界中,使實際的 sequence call 不會干擾 identifier 配置流程:
- Identifier range:
Id = [(hi - n) + 1, hi](n 為 increment size) - 第一次 sequence call 產生 lo value,第二次確定 hi value
- 外部系統可直接使用 sequence call 取得值並插入資料,不會與 Hibernate 的 identifier 配置衝突

Figure 9.3: The pooled optimizer
Pooled-Lo Optimizer#
Pooled-lo 是 pooled 的變體,database sequence value 代表 lo value(range 的下界):
- Identifier range:
Id = [lo, (lo + n) - 1](n 為 increment size) - 第一次 sequence call 產生 lo value,後續以此為基礎配置 identifier
- 同樣可與外部系統互通

Figure 9.4: The pooled-lo optimizer
Optimizer Gain#
使用 optimizer 可顯著減少 identifier 配置的時間:
- Sequence generator + pooled optimizer – 即使 database sequence 本身已很快,pooled optimizer 仍能大幅降低 identifier 產生時間。對 write-intensive 應用,increment size 應根據每次 transaction 插入的列數調整

Figure 9.5: Sequence pooled optimizer gain
- Table generator + pooled optimizer – 由於 row-level locking 和額外的 database connection switch 開銷,table generator 效率低於 database sequence,但 pooled optimizer 同樣能有效改善效能

Figure 9.6: Table pooled optimizer gain
Identifier Generator Performance#
併發環境下各 identifier generator 的效能比較:
- Identity vs. Table – 即使 identity generator 無法使用 JDBC batching,仍然能勝過使用 pooled optimizer(increment size = 100)的 table generator。隨著 thread 數增加,table generator 的效能下降更為明顯

Figure 9.7: Identity vs. Table
- Sequence vs. Table – Sequence generator 既能使用 pooled optimizer 又能搭配 JDBC batch insert,與 table generator 的差距更大。在高併發環境中,table generator 的 row-level locking 和 connection switch 會造成 serial execution

Figure 9.8: Sequence vs. Table
Database sequence 是最有效率的 Hibernate identifier 選擇。它使用輕量級的同步機制,比 row-level locking 的併發控制更具可擴展性,同時允許 sequence call optimizer 且不影響 JDBC batching。