映射组件
目前为止,对象模型的类已经全部成为实体类,每一个都有自己的生命周期和同一性。然而,User类与Address类有着一种特殊的关联。从对象建模的角度来看,这个关联是一种聚集——是整体一部分(part-of)的关系。聚集是一种强健的关联形式;它对于对象的生命周期有一些额外的语义。在这种情况下,我们有一种更强健的形式:复合(composition),在这里部分的生命周期完全依赖于整体的生命周期。
把Address映射为值类型,把User映射为实体。这影响POJO类的实现吗?
Java没有复合的概念——类或者属性无法被标记为组件或者复合。唯一的区别是对象标识符:组件没有独立的同一性,因此持久化组件类不需要标识符属性或者标识符映射。它是一个简单的POJO:
public class Address {
private String street;
private String zipcode;
private String city;
public Address() {
super();
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
User和Address之间的复合是一个元数据级的概念:你只需要告诉Hibernate,Address是映射文档中的值类型,或者是包含注解的值类型。
1、XML格式的组件映射
包含组件Address的User类的映射:<component name="homeAddress" class="Address">
<property name="street" type="string" column="BILLING_STREET" not-null="true"/>
<property name="zipcode" type="string" column="HOME_ZIPCODE" not-null="true"/>
<property name="city" type="string" column="HOME_CITY" not-null="true"/>
</component>
<component name="billingAddress" class="Address">
<property name="street" type="string" column="BILLING_STREET" not-null="true"/>
<property name="zipcode" type="string" column="BILLING_ZIPCODE" not-null="true"></property>
<property name="city" type="string" column="BILLING_CITY" not-null="true"></property>
</component>
在<component>元素的内部声明Address的持久化属性。User类的属性被命名为homeAddress。
重用同一个组件类,把这个类型的另一个属性映射到同一张表。
注意,在这个例子中,你把复合关联建模为单向的。你无法从Address导航到User。Hibernate支持单向的和双向的复合,但是单向复合要常见很多。双向映射的一个例子:
<component name="billingAddress" class="Address">
<parent name="user"/>
<property name="street" type="string" column="BILLING_STREET" not-null="true"/>
<property name="zipcode" type="string" column="BILLING_ZIPCODE" not-null="true"></property>
<property name="city" type="string" column="BILLING_CITY" not-null="true"></property>
</component>
<parent>元素把类型User的属性映射到自己的实体,在这个例子中,它是具名user的那个属性。然后可以调用Address.getUser()在另一个方向进行导航。这真是一个简单的后退指针。
Hibernate组件可以拥有其他的组件,甚至关联到其他实体。这种灵活性是Hibernate支持细粒度的对象模型的基础。例如,可以创建Location类,包含关于Address所有者的家庭住址的详细信息:
<component name="homeAddress" class="Address">
<parent name="user"/>
<component name="location" class="Location">
<property name="streetName" column="HOME_STREETNAME"/>
<property name="streetSide" column="HOME_STREETSIDE"/>
<property name="houseNumber" column="HOME_HOUSENUMBER"/>
<property name="floor" column="HOME_FLOOR"/>
</component>
<property name="zipcode" type="string" column="HOME_ZIPCODE" not-null="true"/>
<property name="city" type="string" column="HOME_CITY" not-null="true"/>
</component>
Location类的设计相当于Address类。你现在有3个类、1个实体和2个值类型,全部被映射到同一张表。
现在用JPA注解映射组件。
2、注解被嵌入的类
Java Persistence规范把组件称为被嵌入的类。为了用注解映射被嵌入的类,可以在自己的实体类中声明一个特定的属性为@Embedded,在这个例子中是User的homeAddress:@Entity
@Table(name = "TBL_USER")
public class User{
...
@Embedded
private Address homeAddress;
...
}
如果没有把一个属性声明为@Embedded,并且它不是JDK类型,Hibernate就在被关联的类中查找@Embedded注解。如果它存在,属性就自动地被映射为一个依赖的组件。
这就是被嵌入的类的样子:
@Embeddable
public class Address {
@Column(name = "ADDRESS_STREET", nullable = false)
private String street;
@Column(name = "ADDRESS_ZIPCODE", nullable = false)
private String zipcode;
@Column(name = "ADDRESS_CITY", nullable = false)
private String city;
public Address() {
super();
}
public Address(String street, String zipcode, String city) {
super();
this.street = street;
this.zipcode = zipcode;
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
有时候,你会想要从外部对一个特定的实体覆盖在被嵌入的类内部所做的设置。例如,下面是如何重名列的例子:
@Entity
@Table(name = "TBL_USER")
public class User{
...
@Embedded
@AttributeOverrides( {
@AttributeOverride(
name = "street",
column = @Column(name = "HOME_STREET")),
@AttributeOverride(
name = "zipcode",
column = @Column(name = "HOME_ZIPCODE")),
@AttributeOverride(
name = "city",
column = @Column(name = "HOME_CITY"))
})
private Address homeAddress;
...
}
User类中的这个新的@Column声明覆盖了被嵌入的类的设置。注意,被嵌入的@Column注解上的所有属性都被替换,因此它不同nullabe = false。
在JPA XML描述符中,被嵌入的类和复合的映射看起来像下面这样:
<entity class="auctin.model.User" access="PROPERTY">
<attributes>
<embedded name="homeAddress">
<attribute-override name="street">
<column name="HOME_STREET"/>
</attribute-override>
<attribute-override name="zipcode">
<column name="HOME_ZIPCODE"/>
</attribute-override>
<attribute-override name="city">
<column name="HOME_CITY"/>
</attribute-override>
</embedded>
</attributes>
</entity>
对于类被映射为组件有2条重要的限制。
第一,就像对于所有的值类型一样,共享引用是不可能的。组件homeAddress没有它自己的数据库同一性(主键),因此无法被除了User的包含实例之外的任何对象引用。
第二,没有优雅的方式表示对Address的空引用。作为任何优雅的方法的替代,Hibernate在组件的所有被映射列中都把空组件表示为空值。这意味着如果保存一个包含全部空属性值的组件对象,Hibernate就会从数据库中获取自己的实体对象时返回一个空组件。