本章内容覆盖了EJB3.0(也就是JPA)实体的注解规范以及Hibernate特有的扩展.
现在EJB3实体Bean是纯粹的POJO.实际上这表达了和Hibernate持久化实体对象同样的概念.
它们的映射都通过JDK5.0注解来定义(EJB3规范已经定义了对应的XML描述语法).
注解分为两个部分,分别是逻辑映射注解和物理映射注解,
通过逻辑映射注解可以描述对象模型,类之间的关系等等,
而物理映射注解则描述了物理的schema,表,列,索引等等.
下面我们在代码中将混合使用这两种类型的注解.
EJB3注解的API定义在 javax.persistence.*包里面.
大部分和JDK5兼容的IDE(象Eclipse, IntelliJ IDEA 和Netbeans等等)都提供了注解接口和属性的自动完成功能.
(这些不需要IDE提供特别的EJB3支持模块,因为EJB3注解是标准的JDK5注解)
请阅读JBoss EJB 3.0指南或者直接阅读Hibernate Annotations测试代码以获取更多的可运行实例.Hibernate Annotations提供的大部分单元测试代码都演示了实际的例子,是一个获取灵感的好地方.
每一个持久化POJO类都是一个实体bean,这可以通过在类的定义中使用 @Entity注解来进行声明:
@Entity
public class Flight implements Serializable {
Long id;
@Id
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
通过 @Entity注解将一个类声明为一个实体bean(即一个持久化POJO类),
@Id注解则声明了该实体bean的标识属性.
其他的映射定义是隐式的.这种以隐式映射为主体,以显式映射为例外的配置方式在新的EJ3规范中处于非常重要的位置,
和以前的版本相比有了质的飞跃.
在上面这段代码中:Flight类映射到Flight表,并使用id列作为主键列.
在对一个类进行注解时,你可以选择对它的的属性或者方法进行注解,根据你的选择,Hibernate的访问类型分别为
field或 property.
EJ3规范要求在需要访问的元素上进行注解声明,例如,如果访问类型为
property就要在getter方法上进行注解声明,
如果访问类型为 field就要在字段上进行注解声明.应该尽量避免混合使用这两种访问类型.
Hibernate根据 @Id 或 @EmbeddedId的位置来判断访问类型.
@Table是类一级的注解,
通过 @Table注解可以为实体bean映射指定表(table),目录(catalog)和schema的名字.
如果没有定义 @Table,那么系统自动使用默认值:实体的短类名(不附带包名).
@Entity
@Table(name="tbl_sky")
public class Sky implements Serializable {
...
@Table元素包括了一个 schema
和一个 catalog属性,如果需要可以指定相应的值.
结合使用 @UniqueConstraint注解可以定义表的唯一约束(unique constraint)
(对于绑定到单列的唯一约束,请参考 @Column注解)
@Table(name="tbl_sky",
uniqueConstraints = ", "day"})}
)
上面这个例子中,在month和day这两个字段上定义唯一约束.
注意 columnNames数组中的值指的是逻辑列名.
Hibernate在NamingStrategy的实现中定义了逻辑列名.
默认的EJB3命名策略将物理字段名当作逻辑字段名来使用.
注意该字段名和它对应的属性名可能不同(如果字段名是显式指定的话).
除非你重写了NamingStrategy,否则不用担心这些区别..
你可以在实体bean中使用 @Version注解,通过这种方式可添加对乐观锁定的支持:
@Entity
public class Flight implements Serializable {
...
@Version
@Column(name="OPTLOCK")
public Integer getVersion() { ... }
}
上面这个例子中,version属性将映射到 OPTLOCK列,
entity manager使用该字段来检测更新冲突(防止更新丢失,请参考last-commit-wins策略).
根据EJB3规范,version列可以是numeric类型(推荐方式)也可以是timestamp类型.
Hibernate支持任何自定义类型,只要该类型实现了UserVersionType.
Every non static non transient property (field or method) of an
entity bean is considered persistent, unless you annotate it as
@Transient. Not having an annotation for your
property is equivalent to the appropriate @Basic
annotation. The @Basic annotation allows you to
declare the fetching strategy for a property:
实体bean中所有的非static非transient的属性都可以被持久化,
除非你将其注解为 @Transient.所有没有定义注解的属性等价于在其上面添加了@Basic注解.
通过 @Basic注解可以声明属性的获取策略(fetch strategy):
public transient int counter; //transient property
private String firstname; //persistent property
@Transient
String getLengthInMeter() { ... } //transient property
String getName() {... } // persistent property
@Basic
int getLength() { ... } // persistent property
@Basic(fetch = FetchType.LAZY)
String getDetailedComment() { ... } // persistent property
@Temporal(TemporalType.TIME)
java.util.Date getDepartureTime() { ... } // persistent property
@Enumerated(STRING)
Starred getNote() { ... } //enum persisted as String in database
上面这个例子中, counter是一个transient的字段,
lengthInMeter的getter方法被注解为 @Transient,
entity manager将忽略这些字段和属性.
而 name, length, firstname
这几个属性则是被定义为可持久化和可获取的.对于简单属性来说,默认的获取方式是即时获取(early fetch).
当一个实体Bean的实例被创建时,Hibernate会将这些属性的值从数据库中提取出来,保存到Bean的属性里.
与即时获取相对应的是延迟获取(lazy fetch).如果一个属性的获取方式是延迟获取
(比如上面例子中的 detailedComment属性),
Hibernate在创建一个实体Bean的实例时,不会即时将这个属性的值从数据库中读出.
只有在该实体Bean的这个属性第一次被调用时,Hibernate才会去获取对应的值.
通常你不需要对简单属性设置延迟获取(lazy simple property),千万不要和延迟关联获取(lazy association fetch)混淆了
(译注:这里指不要把lazy simple property和lazy association fetch混淆了).
为了启用属性级的延迟获取,你的类必须经过特殊处理(instrumented):
字节码将被织入原始类中来实现延迟获取功能,
详情参考Hibernate参考文档.如果不对类文件进行字节码特殊处理,
那么属性级的延迟获取将被忽略.
推荐的替代方案是使用EJB-QL或者Criteria查询的投影(projection)功能.
Hibernate和EJB3都支持所有基本类型的属性映射.
这些基本类型包括所有的Java基本类型,及其各自的wrapper类和serializable类.
Hibernate Annotations还支持将内置的枚举类型映射到一个顺序列(保存了相应的序列值)
或一个字符串类型的列(保存相应的字符串).默认是保存枚举的序列值,
但是你可以通过 @Enumerated注解来进行调整(见上面例子中的note属性).
在核心的Java API中并没有定义时间精度(temporal precision).
因此处理时间类型数据时,你还需要定义将其存储在数据库中所预期的精度.
在数据库中,表示时间类型的数据有 DATE, TIME,
和 TIMESTAMP三种精度(即单纯的日期,时间,或者两者兼备).
可使用 @Temporal注解来调整精度.
@Lob注解表示属性将被持久化为Blob或者Clob类型,
具体取决于属性的类型,
java.sql.Clob,
Character[],
char[] 和
java.lang.String这些类型的属性都被持久化为Clob类型,
而java.sql.Blob,
Byte[],
byte[] 和
serializable类型则被持久化为Blob类型.
@Lob
public String getFullText() {
return fullText;
}
@Lob
public byte[] getFullCode() {
return fullCode;
}
如果某个属性实现了java.io.Serializable同时也不是基本类型,
并且没有在该属性上使用 @Lob注解,
那么Hibernate将使用自带的 serializable类型.
使用 @Column 注解可将属性映射到列.
使用该注解来覆盖默认值(关于默认值请参考EJB3规范).
在属性级使用该注解的方式如下:
不进行注解
和 @Basic一起使用
和 @Version一起使用
和 @Lob一起使用
和 @Temporal一起使用
和
@org.hibernate.annotations.CollectionOfElements一起使用
(只针对Hibernate )
@Entity
public class Flight implements Serializable {
...
@Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }
在上面这个例子中, name属性映射到 flight_name列.
该字段不允许为空,长度为50,并且是不可更新的(也就是属性值是不变的).
上面这些注解可以被应用到正规属性上例如 @Id 或 @Version属性.
@Column(
name="columnName";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0; // decimal precision
int scale() default 0; // decimal scale
name 可选,列名(默认值是属性名)
unique 可选,是否在该列上设置唯一约束(默认值false)
nullable 可选,是否设置该列的值可以为空(默认值false)
insertable 可选,该列是否作为生成的insert语句中的一个列(默认值true)
updatable 可选,该列是否作为生成的update语句中的一个列(默认值true)
columnDefinition 可选: 为这个特定列覆盖SQL DDL片段 (这可能导致无法在不同数据库间移植)
table 可选,定义对应的表(默认为主表)
length 可选,列长度(默认值255)
precision
可选,列十进制精度(decimal precision)(默认值0)
scale
可选,如果列十进制数值范围(decimal scale)可用,在此设置(默认值0)
在实体中可以定义一个嵌入式组件(embedded component),
甚至覆盖该实体中原有的列映射.
组件类必须在类一级定义 @Embeddable注解.
在特定的实体的关联属性上使用 @Embedded和
@AttributeOverride注解可以覆盖该属性对应的嵌入式对象的列映射:
@Entity
public class Person implements Serializable {
// Persistent component using defaults
Address homeAddress;
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
@AttributeOverride(name="name", column = @Column(name="bornCountryName") )
} )
Country bornIn;
...
}
@Embeddable
public class Address implements Serializable {
String city;
Country nationality; //no overriding here
}
@Embeddable
public class Country implements Serializable {
private String iso2;
@Column(name="countryName") private String name;
public String getIso2() { return iso2; }
public void setIso2(String iso2) { this.iso2 = iso2; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
...
}
嵌入式对象继承其所属实体中定义的访问类型
(注意:这可以通过使用Hibernate提供的 @AccessType注解来覆盖原有值)(请参考 linkend="entity-hibspec" />).
在上面的例子中,实体bean Person 有两个组件属性,
分别是 homeAddress和 bornIn.
我们可以看到 homeAddress 属性并没有注解.
但是Hibernate自动检测其对应的Address类中的 @Embeddable注解,
并将其看作一个持久化组件.对于Country中已映射的属性,
则使用 @Embedded和 @AttributeOverride
注解来覆盖原来映射的列名.
正如你所看到的, Address对象中还内嵌了 Country对象,
这里和 homeAddress一样使用了Hibernate和EJB3自动检测机制.
目前EJB3规范还不支持覆盖多层嵌套(即嵌入式对象中还包括其他嵌入式对象)的列映射.
不过Hibernate通过在表达式中使用"."符号表达式提供了对此特征的支持.
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="city", column = @Column(name="fld_city") )
@AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),
@AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )
//nationality columns in homeAddress are overridden
} )
Address homeAddress;
Hibernate注解支持很多EJB3规范中没有明确定义的特性.
例如,可以在嵌入式对象上添加 @MappedSuperclass注解,
这样可以将其父类的属性持久(详情请查阅 @MappedSuperclass).
Hibernate现在支持在嵌入式对象中使用关联注解(如 @*ToOne和 @*ToMany).
而EJB3规范尚不支持这样的用法.你可以使用 @AssociationOverride注解来覆写关联列.
在同一个实体中使用两个同类型的嵌入对象,
其默认列名是无效的:至少要对其中一个进行明确声明.
Hibernate在这方面走在了EJB3规范的前面,
Hibernate提供了NamingStrategy, 在使用Hibernate时,
通过NamingStrategy你可以对默认的机制进行扩展.
DefaultComponentSafeNamingStrategy
在默认的EJB3NamingStrategy上进行了小小的提升,
允许在同一实体中使用两个同类型的嵌入对象而无须额外的声明.