`
阅读更多

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 {
... @Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }

在上面这个例子中,name属性映射到flight_name列. 该字段不允许为空,长度为50,并且是不可更新的(也就是属性值是不变的).
上面这些注解可以被应用到正规属性上例如@Id 或@Version属性.
@Column(
name="columnName"; (1)
boolean unique() default false; (2)
boolean nullable() default true; (3)
boolean insertable() default true; (4)
boolean updatable() default true; (5)
String columnDefinition() default ""; (6)
String table() default ""; (7)
int length() default 255; (8)
int precision() default 0; // decimal precision (9)
int scale() default 0; // decimal scale
(1) name 可选,列名(默认值是属性名)
(2) unique 可选,是否在该列上设置唯一约束(默认值false)
(3) nullable 可选,是否设置该列的值可以为空(默认值false)
(4) insertable 可选,该列是否作为生成的insert语句中的一个列(默认值true)
(5) updatable 可选,该列是否作为生成的update语句中的一个列(默认值true)
(6) columnDefinition 可选: 为这个特定列覆盖SQL DDL片段 (这可能导致无法在不同数据库间移植)
(7) table 可选,定义对应的表(默认为主表)
(8) length 可选,列长度(默认值255)
(8) precision 可选,列十进制精度(decimal precision)(默认值0)
(10) scale 可选,如果列十进制数值范围(decimal scale)可用,在此设置(默认值0)
2.2.2.3. 嵌入式对象(又名组件)
在实体中可以定义一个嵌入式组件(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注解来覆盖原有值)(请参考 Hibernate Annotation Extensions).
在上面的例子中,实体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上进行了小小的提升, 允许在同一实体中使用两个同类型的嵌入对象而无须额外的声明.
2.2.2.4. 无注解之属性的默认值
如果某属性没有注解,该属性将遵守下面的规则:
? 如果属性为单一类型,则映射为@Basic
? 否则,如果属性对应的类型定义了@Embeddable注解,则映射为@Embedded
? 否则,如果属性对应的类型实现了Serializable, 则属性被映射为@Basic并在一个列中保存该对象的serialized版本
? 否则,如果该属性的类型为java.sql.Clob 或 java.sql.Blob,则作为@Lob并映射到适当的LobType.
2.2.. 映射主键属性
使用@Id注解可以将实体bean中的某个属性定义为标识符(identifier). 该属性的值可以通过应用自身进行设置, 也可以通过Hiberante生成(推荐). 使用 @GeneratedValue注解可以定义该标识符的生成策略:
? AUTO - 可以是identity column类型,或者sequence类型或者table类型,取决于不同的底层数据库.
? TABLE - 使用表保存id值
? IDENTITY - identity column
? SEQUENCE - sequence
和EJB3规范相比,Hibernate提供了更多的id生成器.详情请查阅 Hibernate Annotation Extensions .
下面的例子展示了使用SEQ_STORE配置的sequence生成器
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")
public Integer getId() { ... }

下面这个例子使用的是identity生成器
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() { ... }



AUTO生成器适用于可移植的应用(在多个DB间切换). 多个@Id可以共享同一个identifier生成器,只要把generator属性设成相同的值就可以了. 通过@SequenceGenerator 和@TableGenerator,你可以配置不同的identifier生成器. 每一个identifier生成器都有自己的适用范围,可以是应用级(application level)和类一级(class level). 类一级的生成器在外部是不可见的, 而且类一级的生成器可以覆盖应用级的生成器. 应用级的生成器则定义在包一级(package level)(如package-info.java):
@javax.persistence.TableGenerator(
name="EMP_GEN",
table="GENERATOR_TABLE",
pkColumnName = "key",
valueColumnName = "hi"
pkColumnValue="EMP",
allocationSize=20
)
@javax.persistence.SequenceGenerator(
name="SEQ_GEN",
sequenceName="my_sequence"
)
package org.hibernate.test.metadata;

如果在org.hibernate.test.metadata包下面的 package-info.java文件用于初始化EJB配置, 那么该文件中定义的 EMP_GEN 和SEQ_GEN都是应用级的生成器. EMP_GEN定义了一个使用hilo算法 (max_lo为20)的id生成器(该生成器将id的信息存在数据库的某个表中.). id的hi值保存在GENERATOR_TABLE中. 在该表中 pkColumnName"key"等价于 pkColumnValue "EMP", 而valueColumnName "hi"中存储的是下一个要使用的最大值.
SEQ_GEN则定义了一个sequence 生成器, 其对应的sequence名为 my_sequence. 注意目前Hibernate Annotations还不支持sequence 生成器中的 initialValue和 allocationSize参数.
下面这个例子展示了定义在类范围(class scope)的sequence生成器:
@Entity
@javax.persistence.SequenceGenerator(
name="SEQ_STORE",
sequenceName="my_sequence"
)
public class Store implements Serializable {
private Long id;

@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")
public Long getId() { return id; }
}

在这个例子中,Store类使用名为my_sequence的sequence,并且SEQ_STORE 生成器对于其他类是不可见的. 注意在org.hibernate.test.metadata.id包下的测试代码有更多演示Hibernate Annotations用法的例子..
下面是定义组合主键的几种语法:
? 将组件类注解为@Embeddable,并将组件的属性注解为@Id
? 将组件的属性注解为@EmbeddedId
? 将类注解为@IdClass,并将该实体中所有属于主键的属性都注解为@Id
对于EJB2的开发人员来说 @IdClass是很常见的, 但是对于Hibernate的用户来说就是一个崭新的用法. 组合主键类对应了一个实体类中的多个字段或属性, 而且主键类中用于定义主键的字段或属性和 实体类中对应的字段或属性在类型上必须一致.下面我们看一个例子:
@Entity
@IdClass(FootballerPk.class)
public class Footballer {
//part of the id key
@Id public String getFirstname() {
return firstname;
}

public void setFirstname(String firstname) {
this.firstname = firstname;
}

//part of the id key
@Id public String getLastname() {
return lastname;
}

public void setLastname(String lastname) {
this.lastname = lastname;
}

public String getClub() {
return club;
}

public void setClub(String club) {
this.club = club;
}

//appropriate equals() and hashCode() implementation
}

@Embeddable
public class FootballerPk implements Serializable {
//same name and type as in Footballer
public String getFirstname() {
return firstname;
}

public void setFirstname(String firstname) {
this.firstname = firstname;
}

//same name and type as in Footballer
public String getLastname() {
return lastname;
}

public void setLastname(String lastname) {
this.lastname = lastname;
}

//appropriate equals() and hashCode() implementation
}
如上, @IdClass指向对应的主键类.
Hibernate支持在组合标识符中定义关联(就像使用普通的注解一样),而EJB3规范并不支持此类用法.
@Entity
@AssociationOverride( name="id.channel", joinColumns = @JoinColumn(name="chan_id") )
public class TvMagazin {
@EmbeddedId public TvMagazinPk id;
@Temporal(TemporalType.TIME) Date time;

}

@Embeddable
public class TvMagazinPk implements Serializable {
@ManyToOne
public Channel channel;
public String name;
@ManyToOne
public Presenter presenter;
}
2.2.4. 映射继承关系
EJB3支持三种类型的继承映射:
? 每个类一张表(Table per class)策略: 在Hibernate中对应元素:
? 每个类层次结构一张表(Single table per class hierarchy)策略:在Hibernate中对应元素
? 连接的子类(Joined subclasses)策略:在Hibernate中对应 元素
你可以用 @Inheritance注解来定义所选择的策略. 这个注解需要在每个类层次结构(class hierarchy) 最顶端的实体类上使用.
注意
目前还不支持在接口上进行注解.
2.2.4.1. 每个类一张表
这种策略有很多缺点(例如:多态查询和关联),EJB3规范, Hibernate参考手册, Hibernate in Action,以及其他许多地方都对此进行了描述和解释. Hibernate使用SQL UNION查询来实现这种策略. 通常使用场合是在一个继承层次结构的顶端:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable {

这种策略支持双向的一对多关联. 这里不支持IDENTITY生成器策略,因为id必须在多个表间共享. 当然,一旦使用这种策略就意味着你不能使用 AUTO 生成器和IDENTITY生成器.
2.2.4.2. 每个类层次结构一张表
整个继承层次结构中的父类和子类的所有属性都映射到同一个表中, 他们的实例通过一个辨别符(discriminator)列来区分.:
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="planetype",
discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue("Plane")
public class Plane { ... }

@Entity
@DiscriminatorValue("A320")
public class A320 extends Plane { ... }

在上面这个例子中,Plane是父类,在这个类里面将继承策略定义为 InheritanceType.SINGLE_TABLE,并通过 @DiscriminatorColumn注解定义了辨别符列(还可以定义辨别符的类型). 最后,对于继承层次结构中的每个类,@DiscriminatorValue注解指定了用来辨别该类的值. 辨别符列的名字默认为 DTYPE,其默认值为实体名(在@Entity.name中定义),其类型 为DiscriminatorType.STRING. A320是子类,如果不想使用默认的辨别符,只需要指定相应的值即可. 其他的如继承策略,辨别标志字段的类型都是自动设定的.
@Inheritance 和 @DiscriminatorColumn 注解只能用于实体层次结构的顶端.
2.2.4.3. 连接的子类
当每个子类映射到一个表时, @PrimaryKeyJoinColumn 和@PrimaryKeyJoinColumns 注解定义了每个子类表关联到父类表的主键:
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Boat implements Serializable { ... }

@Entity
public class Ferry extends Boat { ... }

@Entity
@PrimaryKeyJoinColumn(name="BOAT_ID")
public class AmericaCupClass extends Boat { ... }

以上所有实体都使用了JOINED策略, Ferry表和Boat表使用同名的主键. 而AmericaCupClass表和Boat表使用了条件 Boat.id = AmericaCupClass.BOAT_ID进行关联.
2.2.4.4. 从父类继承的属性
有时候通过一个(技术上或业务上)父类共享一些公共属性是很有用的, 同时还不用将该父类作为映射的实体(也就是该实体没有对应的表). 这个时候你需要使用@MappedSuperclass注解来进行映射.
@MappedSuperclass
public class BaseEntity {
@Basic
@Temporal(TemporalType.TIMESTAMP)
public Date getLastUpdate() { ... }
public String getLastUpdater() { ... }
...
}

@Entity class Order extends BaseEntity {
@Id public Integer getId() { ... }
...
}
在数据库中,上面这个例子中的继承的层次结构最终以Order表的形式出现, 该表拥有id, lastUpdate 和 lastUpdater三个列.父类中的属性映射将复制到其子类实体. 注意这种情况下的父类不再处在继承层次结构的顶端.
注意
注意,没有注解为@MappedSuperclass的父类中的属性将被忽略.
注意
除非显式使用Hibernate annotation中的@AccessType注解, 否则将从继承层次结构的根实体中继承访问类型(包括字段或方法)
注意
这对于@Embeddable对象的父类中的属性持久化同样有效. 只需要使用@MappedSuperclass注解即可 (虽然这种方式不会纳入EJB3标准)
注意
可以将处在在映射继承层次结构的中间位置的类注解为@MappedSuperclass.
注意
在继承层次结构中任何没有被注解为@MappedSuperclass 或@Entity的类都将被忽略.
你可以通过 @AttributeOverride注解覆盖实体父类中的定义的列. 这个注解只能在继承层次结构的顶端使用.
@MappedSuperclass
public class FlyingObject implements Serializable {

public int getAltitude() {
return altitude;
}

@Transient
public int getMetricAltitude() {
return metricAltitude;
}

@ManyToOne
public PropulsionType getPropulsion() {

return metricAltitude;
}
...
}

@Entity
@AttributeOverride( name="altitude", column = @Column(name="fld_altitude") )
@AssociationOverride( name="propulsion", joinColumns = @JoinColumn(name="fld_propulsion_fk") )
public class Plane extends FlyingObject {
...
}
在上面这个例子中,altitude属性的值最终将持久化到Plane 表的fld_altitude列.而名为propulsion的关联则保存在fld_propulsion_fk外间列.
你可以为@Entity和@MappedSuperclass注解的类 以及那些对象为@Embeddable的属性定义 @AttributeOverride和@AssociationOverride.
2.2.5. 映射实体Bean的关联关系
2.2.5.1. 一对一(One-to-one)
使用@OneToOne注解可以建立实体bean之间的一对一的关联. 一对一关联有三种情况: 一是关联的实体都共享同样的主键, 二是其中一个实体通过外键关联到另一个实体的主键 (注意要模拟一对一关联必须在外键列上添加唯一约束). 三是通过关联表来保存两个实体之间的连接关系 (注意要模拟一对一关联必须在每一个外键上添加唯一约束).
首先,我们通过共享主键来进行一对一关联映射:
@Entity
public class Body {
@Id
public Long getId() { return id; }

@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Heart getHeart() {
return heart;
}
...
}

@Entity
public class Heart {
@Id
public Long getId() { ...}
}

上面的例子通过使用注解@PrimaryKeyJoinColumn定义了一对一关联.
下面这个例子使用外键列进行实体的关联.
@Entity
public class Customer implements Serializable {
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="passport_fk")
public Passport getPassport() {
...
}

@Entity
public class Passport implements Serializable {
@OneToOne(mappedBy = "passport")
public Customer getOwner() {
...
}

上面这个例子中,Customer 通过Customer 表中名为的passport_fk 外键列和 Passport关联. @JoinColumn注解定义了联接列(join column). 该注解和@Column注解有点类似, 但是多了一个名为referencedColumnName的参数. 该参数定义了所关联目标实体中的联接列. 注意,当referencedColumnName关联到非主键列的时候, 关联的目标类必须实现Serializable, 还要注意的是所映射的属性对应单个列(否则映射无效).
一对一关联可能是双向的.在双向关联中, 有且仅有一端是作为主体(owner)端存在的:主体端负责维护联接列(即更新). 对于不需要维护这种关系的从表则通过mappedBy属性进行声明. mappedBy的值指向主体的关联属性. 在上面这个例子中,mappedBy的值为 passport. 最后,不必也不能再在被关联端(owned side)定义联接列了,因为已经在主体端进行了声明.
如果在主体没有声明@JoinColumn,系统自动进行处理: 在主表(owner table)中将创建联接列, 列名为:主体的关联属性名+下划线+被关联端的主键列名. 在上面这个例子中是passport_id, 因为Customer中关联属性名为passport, Passport的主键是id.
The third possibility (using an association table) is very exotic.
第三种方式也许是最另类的(通过关联表).
@Entity
public class Customer implements Serializable {
@OneToOne(cascade = CascadeType.ALL)
@JoinTable(name = "CustomerPassports"
joinColumns = @JoinColumn(name="customer_fk"),
inverseJoinColumns = @JoinColumns(name="passport_fk")
)
public Passport getPassport() {
...
}

@Entity
public class Passport implements Serializable {
@OneToOne(mappedBy = "passport")
public Customer getOwner() {
...
}

Customer通过名为 CustomerPassports的关联表和 Passport关联; 该关联表拥有名为passport_fk的外键列,该 外键指向Passport表,该信息定义为inverseJoinColumn的属性值, 而customer_fk外键列指向Customer表, 该信息定义为 joinColumns的属性值.
这种关联可能是双向的.在双向关联中, 有且仅有一端是作为主体端存在的:主体端负责维护联接列(即更新). 对于不需要维护这种关系的从表则通过mappedBy属性进行声明. mappedBy的值指向主体的关联属性. 在上面这个例子中,mappedBy的值为 passport. 最后,不必也不能再在被关联端(owned side)定义联接列了,因为已经在主体端进行了声明.
你必须明确定义关联表名和关联列名.
2.2.5.2. 多对一(Many-to-one)
在实体属性一级使用@ManyToOne注解来定义多对一关联:
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}

其中@JoinColumn是可选的,关联字段默认值和一对一 (one to one)关联的情况相似, 列名为:主体的关联属性名+下划线+被关联端的主键列名. 在这个例子中是company_id, 因为关联的属性是company, Company的主键是id.
@ManyToOne注解有一个名为targetEntity的参数, 该参数定义了目标实体名.通常不需要定义该参数, 因为在大部分情况下默认值(表示关联关系的属性类型)就可以很好的满足要求了. 不过下面这种情况下这个参数就显得有意义了:使用接口作为返回值而不是常见的实体.
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}

public interface Company {
...

对于多对一也可以通过关联表的方式来映射。 通过@JoinTable注解可定义关联表, 该关联表包含了指回实体表的外键(通过@JoinTable.joinColumns) 以及指向目标实体表的外键(通过@JoinTable.inverseJoinColumns).
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinTable(name="Flight_Company",
joinColumns = @JoinColumn(name="FLIGHT_ID"),
inverseJoinColumns = @JoinColumns(name="COMP_ID")
)
public Company getCompany() {
return company;
}
...
}

2.2.5.3. 集合类型
2.2.5.3.1. 概况
你可以对 Collection ,List (指有序列表, 而不是索引列表), Map和Set这几种类型进行映射. EJB3规范定义了怎么样使用@javax.persistence.OrderBy 注解来对有序列表进行映射: 该注解接受的参数格式:用逗号隔开的(目标实体)属性名及排序指令, 如firstname asc, age desc,如果该参数为空,则默认以id对该集合进行排序. 如果某个集合在数据库中对应一个关联表(association table)的话,你不能在这个集合属性上面使用@OrderBy注解. 对于这种情况的处理方法,请参考Hibernate Annotation Extensions. EJB3 允许你利用目标实体的一个属性作为Map的key, 这个属性可以用@MapKey(name="myProperty")来声明. 如果使用@MapKey注解的时候不提供属性名, 系统默认使用目标实体的主键. map的key使用和属性相同的列:不需要为map key定义专用的列,因为map key实际上就表达了一个目标属性。 注意一旦加载,key不再和属性保持同步, 也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新 (请参考Hibernate Annotation Extensions). 很多人被 和@MapKey弄糊涂了。 其他它们有两点区别.@MapKey目前还有一些限制,详情请查看论坛或者 我们的JIRA缺陷系统。 注意一旦加载,key不再和属性保持同步, 也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新. (Hibernate 3中Map支持的方式在当前的发布版中还未得到支持).
Hibernate将集合分以下几类.
表 2.1. 集合语义
语义 Java实现类 注解
Bag 语义 java.util.List, java.util.Collection @org.hibernate.annotations.CollectionOfElements 或 @OneToMany 或 @ManyToMany
List 语义 java.util.List (@org.hibernate.annotations.CollectionOfElements 或 @OneToMany 或 @ManyToMany) 以及 @org.hibernate.annotations.IndexColumn
Set 语义 java.util.Set @org.hibernate.annotations.CollectionOfElements 或 @OneToMany 或 @ManyToMany
Map 语义 java.util.Map (@org.hibernate.annotations.CollectionOfElements 或 @OneToMany 或 @ManyToMany) 以及 (空 或 @org.hibernate.annotations.MapKey/MapKeyManyToMany(支持真正的map), 或 @javax.persistence.MapKey
从上面可以明确地看到,没有@org.hibernate.annotations.IndexColumn 注解的java.util.List集合将被看作bag类.
EJB3规范不支持原始类型,核心类型,嵌入式对象的集合.但是Hibernate对此提供了支持 (详情参考 Hibernate Annotation Extensions).
@Entity public class City {
@OneToMany(mappedBy="city")
@OrderBy("streetName")
public List getStreets() {
return streets;
}
...
}

@Entity public class Street {
public String getStreetName() {
return streetName;
}

@ManyToOne
public City getCity() {
return city;
}
...
}


@Entity
public class Software {
@OneToMany(mappedBy="software")
@MapKey(name="codeName")
public Map getVersions() {
return versions;
}
...
}

@Entity
@Table(name="tbl_version")
public class Version {
public String getCodeName() {...}

@ManyToOne
public Software getSoftware() { ... }
...
}
上面这个例子中,City 中包括了以streetName排序的Street的集合. 而Software中包括了以codeName作为 key和以Version作为值的Map.
除非集合为generic类型,否则你需要指定targetEntity. 这个注解属性接受的参数为目标实体的class.
2.2.5.3.2. 一对多(One-to-many)
在属性级使用 @OneToMany注解可定义一对多关联.一对多关联可以是双向关联.
2.2.5.3.2.1. 双向(Bidirectional)
在EJB3规范中多对一这端几乎总是双向关联中的主体(owner)端, 而一对多这端的关联注解为@OneToMany( mappedBy=... )
@Entity
public class Troop {
@OneToMany(mappedBy="troop")
public Set getSoldiers() {
...
}

@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop 通过troop 属性和Soldier建立了一对多的双向关联. 在mappedBy端不必也不能再定义任何物理映射
对于一对多的双向映射,如果要一对多这一端维护关联关系, 你需要删除mappedBy元素并将多对一这端的 @JoinColumn的insertable和updatable设置为false. 很明显,这种方案不会得到什么明显的优化,而且还会增加一些附加的UPDATE语句.
@Entity
public class Troop {
@OneToMany
@JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set getSoldiers() {
...
}

@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
2.2.5.3.2.2. 单向(Unidirectional)
通过在被拥有的实体端(owned entity)增加一个外键列来实现一对多单向关联是很少见的,也是不推荐的. 我们强烈建议通过一个联接表(join table)来实现这种关联(下一节会对此进行解释). 可以通过@JoinColumn注解来描述这种单向关联关系.
@Entity
public class Customer implements Serializable {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
public Set getTickets() {
...
}

@Entity
public class Ticket implements Serializable {
... //no bidir
}

Customer 通过 CUST_ID列和Ticket 建立了单向关联关系.
2.2.5.3.2.3. 通过关联表处理单向关联
通过联接表处理单向一对多关联是首选方式.这种关联通过@JoinTable注解来进行描述.
@Entity
public class Trainer {
@OneToMany
@JoinTable(
name="TrainedMonkeys",
joinColumns = { @JoinColumn( name="trainer_id") },
inverseJoinColumns = @JoinColumn( name="monkey_id")
)
public Set getTrainedMonkeys() {
...
}

@Entity
public class Monkey {
... //no bidir
}

上面这个例子中,Trainer通过 TrainedMonkeys表和 Monkey 建立了单向关联. 其中外键trainer_id关联到Trainer (joinColumns), 而外键monkey_id关联到 Monkey (inversejoinColumns).
2.2.5.3.2.4. 默认处理机制
通过联接表来建立单向一对多关联不需要描述任何物理映射. 表名由以下三个部分组成:主表(owner table)表名+下划线+从表(the other side table)表名. 指向主表的外键名:主表表名+下划线+主表主键列名 指向从表的外键名:主表所对应实体的属性名+下划线+从表主键列名 指向从表的外键定义为唯一约束,用来表示一对多的关联关系.
@Entity
public class Trainer {
@OneToMany
public Set getTrainedTigers() {
...
}

@Entity
public class Tiger {
... //no bidir
}

上面这个例子中,Trainer和Tiger 通过联接表 Trainer_Tiger建立单向关联关系, 其中外键trainer_id关联到Trainer (主表表名, _(下划线), trainer id), 而外键trainedTigers_id关联到Tiger (属性名称, _(下划线), Tiger表的主键列名).
2.2.5.3.3. 多对多(Many-to-many)
2.2.5.3.3.1. 定义
你可以通过@ManyToMany注解可定义的多对多关联. 同时,你也需要通过注解@JoinTable描述关联表和关联条件. 如果是双向关联,其中一段必须定义为owner,另一端必须定义为inverse(在对关联表进行更新操作时这一端将被忽略):
@Entity
public class Employer implements Serializable {
@ManyToMany(
targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
cascade={CascadeType.PERSIST, CascadeType.MERGE}
)
@JoinTable(
name="EMPLOYER_EMPLOYEE",
joinColumns={@JoinColumn(name="EMPER_ID")},
inverseJoinColumns={@JoinColumn(name="EMPEE_ID")}
)
public Collection getEmployees() {
return employees;
}
...
}

@Entity
public class Employee implements Serializable {
@ManyToMany(
cascade={CascadeType.PERSIST, CascadeType.MERGE},
mappedBy="employees"
targetEntity=Employer.class
)
public Collection getEmployers() {
return employers;
}
}

至此,我们已经展示了很多跟关联有关的声明定义以及属性细节. 下面我们将深入介绍@JoinTable注解,该注解定义了联接表的表名, 联接列数组(注解中定义数组的格式为{ A, B, C }), 以及inverse联接列数组. 后者是关联表中关联到Employee主键的列(the "other side").
正如前面所示,被关联端不必也不能描述物理映射: 只需要一个简单的mappedBy参数,该参数包含了主体端的属性名,这样就绑定双方的关系.

分享到:
评论

相关推荐

    EJB中ejb-jar——xml文件说明.pdf

    EJB中ejb-jar文件配置详解 EJB(Enterprise JavaBean)是一种Java技术,用于开发企业级应用程序。EJB容器提供了许多功能,如事务处理、安全认证、资源管理等,以便开发者更方便地开发企业级应用程序。在EJB中,ejb-...

    JSP + EJB + Servlet 实例

    使用原始的servlet操作熟悉EJB的实体管理。 本实例主要功能:实现对员工、角色、账号、部门的关系管理。 本实例的实体设计:部门-员工:一对多。员工-账号:一对一。账号-角色:一对一。 主要采用EJB进行会话管理。...

    经典JAVA.EE企业应用实战.基于WEBLOGIC_JBOSS的JSF_EJB3_JPA整合开发.pdf

    中文名: 经典Java EE企业应用实战--基于WebLogic/JBoss的JSF+EJB 3+JPA整合开发 原名: 经典Java EE企业应用实战--基于WebLogic/JBoss的JSF+EJB 3+JPA整合开发 作者: 李刚 资源格式: PDF 版本: 第一版 出版社: 电子...

    EJB3的三本好书第3本 Mastering EJB3 4ed

    EJB3的三本好书之三,也很不错的一本ejb3的书籍,是英文版,附带源代码,这本书的好处是与Mastering EJB 3ed有一定的继承性,可以对比来看. 3本书的地址 1. Beginning EJB3 Application Development From Novice to ...

    EJB3的三本好书第2本, EJB3 in Action 2007

    EJB3的三本好书第二本,最好的介绍ejb3的书,看过之后,其他的书都送人了,而且附带的源代码,几乎包括了所有的主流应用服务器的例子,glassfish, jboss, weblogic, oracleAS 3本书分别是: 1. Beginning EJB3 ...

    实战EJB 实战EJB 实战EJB

    实战EJB 实战EJB 实战EJB 电子书 pdf

    JSF1.2+EJB3.0实现的一个项目实例

    ejb端 jsfejb3-ejb |-- src |-- org.qiujy.ejb3.dao 持久层DAO接口包 |-- org.qiujy.ejb3.dao.impl 持久层DAO接口实现类包(是ejb3中的会话Bean) |-- org.qiujy.ejb3.entity 实体域模型类包...

    ejb3.0实例教程

    期待已久的 EJB3.0 最终规范已经发布了。虽然 EJB3.0 最终规范出来了一段时间,但对 EJB3.0 的应用还停留在介 绍之中,应用实例更是少之又少,所以作者拟写本书,以简单的实例展现 EJB3.0 的开发过程,希望对大家有所...

    javax.ejb.jar下载

    Files contained in javax.ejb.jar: META-INF/MANIFEST.MF javax.ejb.AccessLocalException.class javax.ejb.AccessTimeout.class javax.ejb.ActivationConfigProperty.class javax.ejb.AfterBegin.class javax....

    EJB设计模式概述——讲解EJB设计模式的重要性及常用的模式

    当采用实体EJB作为数据的抽象层时,如果直接让客户端程序与实体EJB交互,会产生实现一个业务需求便需要大量的EJB属性操作(如下图1)。这直接导致如下问题:网络负载大(远程客户端时)、并发性能低、...

    EJB3 PPT教程

    自己总结的EJB3上课教案,包括SessionBean、EntityBean、MDB、O/R映射与继承映射、持久化实体管理器、EJB3-QL、JTA等的教案.

    java的ejb.jar包

    javax.ejb.AccessLocalException.class javax.ejb.CreateException.class javax.ejb.DuplicateKeyException.class javax.ejb.EJBContext.class javax.ejb.EJBException.class javax.ejb.EJBHome.class javax.ejb....

    javax.ejb.rar

    META-INF / maven / org.glassfish.main.ejb / javax.ejb / pom.properties META-INF / maven / org.glassfish.main.ejb / javax.ejb / pom.xml javax.ejb.AccessLocalException.class javax.ejb.AccessTimeout....

    精通EJB3.0 中文版 3/3

    《精通EJB3.0》共分为4个部分:第一部分对EJB编程基础进行介绍,概要性地对EJB进行了阐述;第二部分重点关注EJB编程的具体内容和过程,向开发者展示了如何使用会话Bean、将会话Bean发布为Web服务、消息驱动Bean;第...

    EJB方面 ejb pdf

    EJB方面EJB方面EJB方面EJB方面EJB方面

    经典JAVA EE企业应用实战基于WEBLOGIC JBOSS的JSF+EJB 3+JPA整合开发——源码.part1

    经典JAVA EE企业应用实战基于WEBLOGIC JBOSS的JSF+EJB 3+JPA整合开发——源码.part1 其他部分详见我的上传列表,全部分卷下载完成才能解压。 本书介绍了Java EE规范的三大主要规范JSF、EJB 3和JPA,其中JSF是Sun...

    Java中的EJB编程实例代码

    Java中的EJB编程实例代码,内容有:简单的EJB、无状态SessionBean、有状态SessionBean、BMP位图实例、cmp实例、Message-Driven Bean、JNDI的使用、112各种EJB之间的调用、B-S结构EJB、 C-S结构EJB、UML建模与J2EE...

    精通EJB3.0 中文版 1/3

    《精通EJB3.0》共分为4个部分:第一部分对EJB编程基础进行介绍,概要性地对EJB进行了阐述;第二部分重点关注EJB编程的具体内容和过程,向开发者展示了如何使用会话Bean、将会话Bean发布为Web服务、消息驱动Bean;第...

    ejb3.0培训教程与源码(绝对精品)

    ejb3.0培训教程与源码(绝对精品) .................... 1, 服务器端的一个组件模型,能部署在多层环境中; EJB API, javax.ejb.*; 2, EJB容器可以为我们提供中间件服务; 3, EJB客户端:(常见的) 1) 独立的java程序...

    ejb3中文版

    ejb3中文版

Global site tag (gtag.js) - Google Analytics