第 2 章 实体Bean
2.1. 简介
本章内容覆盖了EJB3.0实体bean的注解规范以及Hibernate特有的扩展.
2.2. 用EJB3注解进行映射
现在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提供的大部分单元测试代码都演示了实际的例子,是一个获取灵感的好地方.
2.2.1. 声明实体bean
每一个持久化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的位置来判断访问类型.
2.2.1.1. 定义表(Table)
@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 = {@UniqueConstraint(columnNames={"month", "day"})}
)
上面这个例子中,在month和day这两个字段上定义唯一约束. 注意columnNames数组中的值指的是逻辑列名.
Hibernate在NamingStrategy的实现中定义了逻辑列名. 默认的EJB3命名策略将物理字段名当作逻辑字段名来使用. 注意该字段名和它对应的属性名可能不同(如果字段名是显式指定的话). 除非你重写了NamingStrategy,否则不用担心这些区别..
2.2.1.2. 乐观锁定版本控制
你可以在实体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.
2.2.2. 映射简单属性
2.2.2.1. 声明基本的属性映射
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类型.
2.2.2.2. 声明列属性
使用 @Column 注解可将属性映射到列. 使用该注解来覆盖默认值(关于默认值请参考EJB3规范). 在属性级使用该注解的方式如下:
? 不进行注解
? 和 @Basic一起使用
? 和 @Version一起使用
? 和 @Lob一起使用
? 和 @Temporal一起使用
? 和 @org.hibernate.annotations.CollectionOfElements一起使用 (只针对Hibernate )
@Entity
public class Flight implements Serializable {
...

