組件** 是一種**值類型**,不具備獨立標識符(ID),生命週期完全依賴於擁有它的實體。資料庫層面,組件的字段直接扁平化映射到擁有實體的表中。**適用場景**:地址、姓名、金額幣別、時間區間等可復用的值對象。
@Embeddable:標記組件類(如 Address)。@Embedded:在實體中引用組件(如 Person.homeAddress)。@AttributeOverride / @AttributeOverrides:覆蓋組件字段的列映射(列名、類型等)。@Embeddable
public class Address {
private String city;
private String street;
private String zipCode;
// getters/setters
}
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
private Address homeAddress; // 直接扁平化到 person 表
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "work_city")),
@AttributeOverride(name = "street", column = @Column(name = "work_street")),
@AttributeOverride(name = "zipCode", column = @Column(name = "work_zip"))
})
private Address workAddress; // 同類型組件第二次使用,必須覆蓋列名
}
@Embeddable
public class Address {
private String city;
@Embedded private Country country; // 嵌套組件
}
@Entity
public class Person {
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "home_city")),
@AttributeOverride(name = "country.name", column = @Column(name = "home_country_name")), // 使用點號路徑
@AttributeOverride(name = "country.isoCode", column = @Column(name = "home_country_iso"))
})
private Address homeAddress;
}
注意:JPA 標準不支援多層嵌套覆蓋,Hibernate 透過點號路徑(country.name)擴充支援。
@Embeddable
public class OrderItem {
@ManyToOne
@JoinColumn(name = "product_id")
private Product product; // 組件中可持有關聯
private int quantity;
private BigDecimal price;
}
<component name="propertyName" class="ComponentClass"><property name="field" column="COL_NAME"/><parent name="owningEntityRef"/>:組件反向引用擁有實體(罕用)。<class name="Person" table="person">
<id name="id" column="pid"><generator class="native"/></id>
<property name="name"/>
<component name="homeAddress" class="Address">
<property name="city" column="home_city"/>
<property name="street" column="home_street"/>
<property name="zipCode" column="home_zip"/>
</component>
<component name="workAddress" class="Address">
<property name="city" column="work_city"/>
<property name="street" column="work_street"/>
<property name="zipCode" column="work_zip"/>
</component>
</class>
<set name="addresses" table="person_addresses">
<key column="person_id"/>
<composite-element class="Address">
<property name="city"/>
<property name="street"/>
</composite-element>
</set>
關鍵:Set 要求組件正確實現 equals()/hashCode();組件不可包含集合,需用 <nested-composite-element> 或改為一對多實體映射。
<composite-map-key class="Address">:組件作 Map 鍵,需實現 equals/hashCode。<composite-id name="id" class="OrderLineId">:組件作複合主鍵,類必須實現 Serializable + equals/hashCode。<dynamic-component name="attrs"> 映射 Map<String, Object>,部署期通過修改映射文件動態增減屬性,無需改代碼。運行期可透過 Configuration 對象操作元模型。
@Entity
public class Husband {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
private Wife wife; // Wife 類無註解,Hibernate 自動檢測 @Embeddable
}
@Embeddable // 必須加,否則視為實體關聯
public class Wife {
private String wifeId;
private String wifeName;
private String age;
// getters/setters
}
// 測試
@Test
public void testSchemaExport() {
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
}
// 生成表 husband:id, name, wifeId, wifeName, age
| 坑點 | 後果 | 解決 |
|---|---|---|
組件類漏加 @Embeddable | Hibernate 視為實體關聯,產生外鍵、獨立表 | 必加 @Embeddable |
| 同實體引用兩次相同組件類未覆蓋列 | 列名衝突、啟動報錯 | 用 @AttributeOverride 區分列名 |
組件用於 Set 未實現 equals/hashCode | 去重失效、刪除異常 | 基於所有字段實現 equals/hashCode |
| 組件包含集合 | 映射不支援 | 改用一對多實體或 <nested-composite-element> |
組件映射實現**細粒度對象模型**,消除「貧血模型」中大量 getter/setter 的原始類型字段,提升領域模型表達力。註解映射更簡潔,XML 映射適合舊專案或需外部化配置場景。