6.2.11 用@Table和@SecondaryTable进行数据库表映射
可以使用@Table注解指定数据库中用来进行实体持久化的表的细节。如果省略这个注解,Hibernate就使用类名作为表名;所以只有在希望覆盖默认行为时,才需要提供这个注解,@Table注解支持4个属性,可以覆盖表名、编目和模式,还可以在表列上施加唯一约束。在通常情况下,只提供表名,比如@Table(name=”ORDER_HISTORY”)。如果数据库模式是从带注解的类生成的,而且需要补充任何列特有的约束,那么就要应用唯一约束(参见本章后面对@Column和@JoinColumn的讨论)。否则,不施加这些约束。
@SecondaryTable注解可以将实体bean持久化到几个不同的数据库表中。在这种情况下,除了为主数据库表提供@Table注解之外,实体bean还具有一个@SecondaryTable注解或@SecondaryTables注解(@SecondaryTables注解中包含零个或多个@SecondaryTable注解)。@SecondaryTable注解的基本属性与@Table注解相同,但是增加了join属性。join属性定义与主数据库表进行联结的联结列。join属性的值是javax.persistence.PrimaryKeyJoinColumn对象的数组。如果省略join属性,那么表根据同名的主键列表进行联结。
当从辅助表获取实体中的属性时,必须给它标上@Column注解,并且用表属性标识适当的表。代码清单6-10演示如何从辅助表获取Customer实体的一个属性。
代码清单6-10 跨两个表映射的实体的字段访问示例
package com.speakermore.beginhb.ch06;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
@Entity
@Table(name="Customer")
@SecondaryTable(name="Customer_Details")
public class Customer implements Serializable {
@Id
public int id;
public String name;
@Column(table="Customer_Details")
public String address;
}
对于主表或辅助表中的一,可以在@Table或@SecondaryTable的uniqueConstraints属性中添加一个或多个适当的@UniqueConstraint注解,从而指定它们具有唯一的值。例如,使用以下代码将前面声明中的name字段标为唯一的:
@Entity
@Table(
name="Customer"
uniqueConstraints={@UniqueConstraint(columnNames=”name”)}
)
@SecondaryTable(name="Customer_Details")
public class Customer implements Serializable {
…
}
在默认情况下,POJO中的属性和实例变量会被持久化----Hibernate会替你存储它们的值。因此,最简单的映射是对“基本”类型的映射。这些类型包括基本数据类型、包装类、它们的数组、枚举及其它任何实现了Serializable但不是实体的类型。这些类型都被隐式地映射,不需要注解。在默认情况下,这样的字段会映射到单一列,并即时读取(当从数据库中获取这个实体时,会同时获取所有基本字段和属性)。另外,如果字段或属性不是基本数据类型,那么可以作为null值存储和获取。
通过在适当的类成员上应用@Basic注解,可以覆盖这种默认行为。这个注解有两个可选属性:一个叫option,它的值类型是boolean,默认为true。如果将这个属性设置为false,那么在创建数据库模式时,相关联的列应该设置为NOT NULL。第二个属性称为fetch,它的值是FetchType枚举的一个成员,默认值是EAGER。如果设置为LAZY,就会提示在访问相关联的列时进行加载。使用延迟加载在一般情况下意义不大,除非是将大型可序列化对象作为基本类型进行映射(而不是为它们本身设置实体映射),在这种情况下使用延迟加载可以减少获取实体所用的时间。默认值EAGER一定会生效,但是LAZY标志只是一个提示,持久化引擎可以忽略这个标志。
常常省略@Basic注解,并且使用@Column注解替代@Basic注解的可选属性,除非是需要设置NOT NULL行为
一些字段可能只在运行时使用,在对象持久化到数据库中时应该从对象中去掉它们。EJB 3规范为这些瞬时字段提供了@Transient注解。它没有任何属性,只需要根据实体bean的属性访问策略,将它添加到实例变量或获取器方法上即可。
在标例中,在Book类中添加一个名为publicationDate的Date字段,这个字段不需要存储在数据库中。因此,将这个字段标为瞬时的:
@Transient
public Date getPublicationDate(){
return publicationDate;
}
@Column注解用来指定字段或属性映射到的列的细节。一些细节是与数据库相关的,因此只在从带注解的文件生成模式时使用这些信息。其他信息由Hibernate(或EJB 3持久化引擎)在运行时应用。它是可选的,有一组合理的默认值:但是在覆盖默认行为,或者对象模型需要适应现有的数据库模式(默然说话:例如:实体的属性名与数据库表的列名不一致)时,常常需要使用这个注解。它比@Basic注解更常用。通常需要为以下属性提供值:
v name允许显式地指定列名——默认情况下,以属性名作为列名。但如果SQL关键字与默认的列名冲突(比如user),就必须覆盖默认的列名。
v length允许显式地指定用来映射值的列的大小(尤其是对String值)。默认列大小是255,这可能会导致String数据被截断(默然说话:还记得那个有名的“将截断二进制字符串”的SQL错么?255也许对于大多数字符串是足够的,但如果你是一篇文章的内容呢?)
v nullable可以在生成模式(默然说话:再次强调,模式就是表!)时将列标为NOT NULL。在默认情况下,字段应允许为空,但是,如果字段是必须的,就需要覆盖这个设置。
v unique指定列只包括唯一的值。默认设置是false,但是有的非主键字段如果出现重复的值,也会导致问题(比如用户名),在这种情况下就需要设置unique属性。
我们给Book实体的标题字段加上@Column注解,并且指定了其中3个属性:
@Column(name="working_title",length=200,nullable=false)
public String getTitle() {
return title;
}
还有一些不太常用的属性:
v 当实体跨一个或多个辅助表进行映射时,使用table属性。在默认情况下,从主表中获取值,但是可以在这里指定辅助表之一(参见本章@SecondaryTable注解示例)。
v insertable属性的默认值是true。如果设置为false,那么Hibernate生成的插入语句就会忽略注解的字段(也就是不对它进行持久化)。
v updateable属性的默认值是true,如果设置为false,那么Hibernate生成的插入语句就会忽略注解的字段(也就是持久化后不再修改它)。
v columnDefinition属性的默认值可以设置为DDL片段,这个DDL片段用来在数据库中生成这个列。这个属性只在从带注解的实体生成模式时使用,而且应该尽可能避免使用它,因为它可能损害应用程序在不同数据库之间的可移植性。
v precision属性可以在生成模式时指定小数数字列的精度,如果持久化的值不是小数,就会忽略这个属性。这个属性的值表示数字中的位数(通常需要的最小长度是n+1,其中的n是刻度)。
v scale属性可以在生成模式时指定小数数字列的刻度,如何持久化的值不是小数,就会忽略这个属性。这个属性的值表示小数点后面的位数。
EJB 3支持一对一,一对多,多对一和多对多关联。每种关联类型都有对应的注解。在本节中,我们将讨论如何用注解建立各种关联。
1.对嵌入实体(组件)的一对一关联进行映射
如果一个实体的所有字段都维护在与另一个实体相同的表中,那么在Hibernate中就将这个实体称为组件(component)。EJB 3标准将这样的实体称为嵌入的(embedded)实体。
@Embedded和@Embeddable注解用来管理这样的关系。在这个图书数据库示例中,我们按照这种方式在AuthorAddress类和Author类之间建立关联。
AuthorAddress标有@Embeddable注解。可嵌入的实体必须完全由基本数据类型的属性组成。可嵌入的实体只能使用@Basic,@Lob,@Temporal和@Enumerated注解。它不能用Id注解维护自己的主键,因为它的主键就是包含它的实体的主键。
(默然说话:其实嵌入实体就是把一个实体A中的一部分属性单独又做成一个类B,然后在A里声明一个B类型的属性,这样的做法比较适合那种字段多的表,而我们所需要的数据常常只是几个字段,那我们可以把这几个字段独立为一个实体,再把它嵌入到对应表的实体类中。)
@Embeddable本身是一个纯粹的标志性注解,它没有任何属性。通常情况下,可嵌入实体的属性不需要进一步注解。
代码清单6-11 表示一个实体可以嵌入其他实体中
@Embeddable
public class AuthorAddress {
}
然后,包含可嵌入实体的实体应该给适当的字段或getter方法加注解,在使用可嵌入实体的地方加上@Embedded注解,如代码清单6-12所示。
代码清单6-12 标出一个嵌入的属性
@Embedded
public AuthorAddress getAddress(){
return this.address;
}
@Embedded注解会从嵌入的类型获得它的列信息,但是允许用@AttributeOverride和@AttributeOverrides注解覆盖特定的列。例如,代码清单6-13将AuthorAddress的address和country属性的默认列名改为ADDR和NATION
代码清单6-13 覆盖嵌入属性的默认属性
@Embedded
@AttributeOverrides({
@AttributeOverride(name=”address”,column=@Column(name=”ADDR”)),
@AttributeOverride(name=”country”,column=@Column(name=”NATION”))
})
public AuthorAddress getAddress(){
return this.address;
}
Hibernate和EJB 3标准都不支持跨多个表映射嵌入的实体。应该通过传统的一对一关联映射它。
2.对传统的一对一关联进行映射
在使用@OneToOne注解之前,应该考虑是否使用前面介绍的嵌入技术,因为这样的关系常有些危险性。
如果你决定以这种方式声明关联(可能是因为打算以后将它改为一对多或多对多关系),那么应用注解是非常容易的——所有属性都是可选的。代码清单6-14演示如何声明这样的关系。
代码清单6-14 声明简单的一对一关系
@OneToOne
public Address getAddress(){
return this.address;
}
@OneToOne注解允许指定以下可选属性:
v targetEntity可以设置为存储这个注解的实体的类。如果不设置这个属性,就从字段的类型或getter的返回类型推断出适当的类型。
v cascade可以设置为javax.persistence.CascadeType枚举的任何成员。默认情况下不设置这个属性。参见下面的“级联操作”中对这些值的讨论。
v fetch可以设置为FetchType的EAGER或LAZY
v optional表示映射的值是否可空。
v mappedBy表示一个双向一对一关系由指定的实体拥有。拥有关系的实体包含从属实体的主键。
级联操作
如果在两个实体之间建立了关联(例如Human和Pet之间的一对一关联),那么在对一个实体执行某些持久化操作时,常常希望这些操作也应用于它链接到的实体。请考虑以下代码:
Human dave=new Human("dave");
Pet cat=new PetCat(“Tibbles”);
dave.setPet(cat);
session.save(dave);
在最后一行(用粗体突出显示),我们可能希望保存与Human对象关联的Pet对象。在一对一关系中,我们常常希望实体上的所有操作都传播(即级联)到从属实体。但是在其他关联中,这不是我们需要的效果;甚至在一对一关系中,也可能由于特殊的原因,不希望级联删除从属实体(可能是为了进行审计)。
因此,可以使用级联注解指定哪些类型的操作应该通过关联级联到另一个实体。这个注解接受一个由CascadeType枚举成员组成的数组。这些成员与EJB 3持久化所用的EntityManager类的方法名对应,也大致对应于实体上的操作类型:
v ALL:要求所有操作都级联到从属实体。这相当于同时包含MERGE、PERSIST、REFRESH和REMOVE。
v MERGE:级联更新实体在数据库中的状态(即UPDATE…)。
v PERSIST:级联地对实体在数据库中的状态进行最初存储(即INSERT…)。
v REFRESH:级联地从数据库中获取实体的状态(即SELECT…)。
v REMOVE:级联地从数据库中删除实体的状态(即