- 浏览: 268887 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
xurun:
点击节点的时候出错,怎么办??1120行,data为空为或不是 ...
TreeGrid -
QQ371496669:
引用load方式检索不到的话会抛出org.hibernate. ...
Hibernate中No row with the given identifier exists -
汽车城路:
我现在正在使用这个Extjs,目前也遇到了这个问题,就是怎么换 ...
ext使用--Panel和iframe联合使用时页面高度的解决方法 -
jsjzhou:
有必要. 在实际项目我们就采用了HttpUrlConnecti ...
HttpURLConnection VS HttpClient性能测试 -
小猪笨笨:
不错,通俗易懂
在oracle中创建unique唯一约束(单列和多列)
本章内容覆盖了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 = {@UniqueConstraint(columnNames={"month", "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上进行了小小的提升,允许在同一实体中使用两个同类型的嵌入对象而无须额外的声明.
如果某属性没有注解,该属性将遵守下面的规则:
如果属性为单一类型,则映射为@Basic
否则,如果属性对应的类型定义了@Embeddable注解,则映射为@Embedded
否则,如果属性对应的类型实现了Serializable,
则属性被映射为@Basic并在一个列中保存该对象的serialized版本
否则,如果该属性的类型为java.sql.Clob 或 java.sql.Blob,则作为@Lob并映射到适当的LobType.
xreflabel="Mapping identifier properties">
使用@Id注解可以将实体bean中的某个属性定义为标识符(identifier).
该属性的值可以通过应用自身进行设置,也可以通过Hiberante生成(推荐).
使用 @GeneratedValue注解可以定义该标识符的生成策略:
AUTO - 可以是identity column类型,或者sequence类型或者table类型,取决于不同的底层数据库.
TABLE - 使用表保存id值
IDENTITY - identity column
SEQUENCE - sequence
和EJB3规范相比,Hibernate提供了更多的id生成器.详情请查阅 .
下面的例子展示了使用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).
类一级的生成器在外部是不可见的,而且类一级的生成器可以覆盖应用级的生成器.
应用级的生成器则定义在XML级(请参阅):
<table-generator name="EMP_GEN"
table="GENERATOR_TABLE"
pk-column-name="key"
value-column-name="hi"
pk-column-value="EMP"
allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.TableGenerator(
name="EMP_GEN",
table="GENERATOR_TABLE",
pkColumnName = "key",
valueColumnName = "hi"
pkColumnValue="EMP",
allocationSize=20
)
<sequence-generator name="SEQ_GEN"
sequence-name="my_sequence"
allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.SequenceGenerator(
name="SEQ_GEN",
sequenceName="my_sequence",
allocationSize=20
)
如果JPA XML(如META-INF/orm.xml)用于定义生成器, 那么该文件中定义的 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生成器,
其使用名为my_sequence的sequence.
该hilo算法基于sequence,该sequence分配的大小为20.
注意,现在这个版本还不能处理sequence生成器的initialValue属性.
默认分配的大小为50,因此如果你打算使用sequence,并且希望每次都重新获取新的值,务必将
分配的大小设置为1.
EJB3.0规范已经不再支持Package级别的定义. 但是你仍然可以在包上使用
@GenericGenerator注解(详情请参考 linkend="entity-hibspec-identifier" />).
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;
}
EJB3支持三种类型的继承映射:
每个类一张表(Table per class)策略: 在Hibernate中对应<union-class>元素:
每个类层次结构一张表(Single table per class hierarchy)策略:在Hibernate中对应<subclass>元素
连接的子类(Joined subclasses)策略:在Hibernate中对应 <joined-subclass>元素
你可以用 @Inheritance注解来定义所选择的策略.
这个注解需要在每个类层次结构(class hierarchy) 最顶端的实体类上使用.
目前还不支持在接口上进行注解.
这种策略有很多缺点(例如:多态查询和关联),EJB3规范, Hibernate参考手册,
Hibernate in Action,以及其他许多地方都对此进行了描述和解释.
Hibernate使用SQL UNION查询来实现这种策略.
通常使用场合是在一个继承层次结构的顶端:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable {
这种策略支持双向的一对多关联.
这里不支持IDENTITY生成器策略,因为id必须在多个表间共享.
当然,一旦使用这种策略就意味着你不能使用
AUTO 生成器和IDENTITY生成器.
整个继承层次结构中的父类和子类的所有属性都映射到同一个表中,他们的实例通过一个辨别符(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 注解只能用于实体层次结构的顶端.
当每个子类映射到一个表时, @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进行关联.
有时候通过一个(技术上或业务上)父类共享一些公共属性是很有用的,同时还不用将该父类作为映射的实体(也就是该实体没有对应的表).
这个时候你需要使用@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.
使用@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的属性值.
你必须明确定义关联表名和关联列名.
在实体属性一级使用@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}, role="bold">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;
}
...
}
你可以对 Collection ,List(指有序列表, 而不是索引列表),Map和Set这几种类型进行映射.
EJB3规范定义了怎么样使用@javax.persistence.OrderBy
注解来对有序列表进行映射:
该注解接受的参数格式:用逗号隔开的(目标实体)属性名及排序指令,如firstname asc, age desc,如果该参数为空,则默认以id对该集合进行排序.
如果某个集合在数据库中对应一个关联表(association table)的话,你不能在这个集合属性上面使用@OrderBy注解.
对于这种情况的处理方法,请参考.
EJB3 允许你利用目标实体的一个属性作为Map的key,
这个属性可以用@MapKey(name="myProperty")来声明.
如果使用@MapKey注解的时候不提供属性名,
系统默认使用目标实体的主键.
map的key使用和属性相同的列:不需要为map key定义专用的列,因为map key实际上就表达了一个目标属性.
注意一旦加载,key不再和属性保持同步,也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新
(请参考).
很多人被<map>和@MapKey弄糊涂了.
其他它们有两点区别.@MapKey目前还有一些限制,详情请查看论坛或者我们的JIRA缺陷系统.
注意一旦加载,key不再和属性保持同步,也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新.
(Hibernate 3中Map支持的方式在当前的发布版中还未得到支持). Hibernate将集合分以下几类.
语义
Java实现类
注解
Bag 语义
java.util.List, java.util.Collection
@org.hibernate.annotations.CollectionOfElements 或
@OneToMany 或 @ManyToMany
带主键的Bag语义(没有普通Bag语义的限制)
java.util.List, java.util.Collection
(@org.hibernate.annotations.CollectionOfElements 或
@OneToMany 或 @ManyToMany) 和 @CollectionId
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对此提供了支持
(详情参考 ).
@Entity public class City {
@OneToMany(mappedBy="city")
@OrderBy("streetName")
public List<Street> 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<String, Version> 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.
在属性级使用 @OneToMany注解可定义一对多关联.一对多关联可以是双向关联.
在EJB3规范中多对一这端几乎总是双向关联中的主体(owner)端,而一对多这端的关联注解为@OneToMany( mappedBy=...
)
@Entity
public class Troop {
@OneToMany(mappedBy="troop")
public Set<Soldier> 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<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
通过在被拥有的实体端(owned entity)增加一个外键列来实现一对多单向关联是很少见的,也是不推荐的.
我们强烈建议通过一个联接表(join table)来实现这种关联(下一节会对此进行解释).
可以通过@JoinColumn注解来描述这种单向关联关系.
@Entity
public class Customer implements Serializable {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
public Set<Ticket> getTickets() {
...
}
@Entity
public class Ticket implements Serializable {
... //no bidir
}
Customer 通过CUST_ID列和Ticket 建立了单向关联关系.
通过联接表处理单向一对多关联是首选方式.这种关联通过@JoinTable注解来进行描述.
@Entity
public class Trainer {
@OneToMany
@JoinTable(
name="TrainedMonkeys",
joinColumns = { @JoinColumn( name="trainer_id") },
inverseJoinColumns = @JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
@Entity
public class Monkey {
... //no bidir
}
上面这个例子中,Trainer通过TrainedMonkeys表和 Monkey 建立了单向关联.
其中外键trainer_id关联到Trainer(joinColumns), 而外键monkey_id关联到 Monkey
(inversejoinColumns).
通过联接表来建立单向一对多关联不需要描述任何物理映射.
表名由以下三个部分组成:主表(owner table)表名+下划线+从表(the other side table)表名.
指向主表的外键名:主表表名+下划线+主表主键列名
指向从表的外键名:主表所对应实体的属性名+下划线+从表主键列名
指向从表的外键定义为唯一约束,用来表示一对多的关联关系.
@Entity
public class Trainer {
@OneToMany
public Set<Tiger> getTrainedTigers() {
...
}
@Entity
public class Tiger {
... //no bidir
}
上面这个例子中,Trainer和Tiger通过联接表 Trainer_Tiger建立单向关联关系,其中外键trainer_id关联到Trainer(主表表名, _(下划线), trainer id),而外键trainedTigers_id关联到Tiger(属性名称, _(下划线), Tiger表的主键列名).
你可以通过@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参数,该参数包含了主体端的属性名,这样就绑定双方的关系.
和其他许多注解一样,在多对多关联中很多值是自动生成.
当双向多对多关联中没有定义任何物理映射时,Hibernate根据以下规则生成相应的值.
关联表名:主表表名+_下划线+从表表名,关联到主表的外键名:主表名+_下划线+主表中的主键列名.
关联到从表的外键名:主表中用于关联的属性名+_下划线+从表的主键列名.
以上规则对于双向一对多关联同样有效.
@Entity
public class Store {
@ManyToMany(cascade = CascadeType.PERSIST)
public Set<City> getImplantedIn() {
...
}
}
@Entity
public class City {
... //no bidirectional relationship
}
上面这个例子中,Store_City作为联接表.
Store_id列是联接到Store表的外键.
而implantedIn_id列则联接到City表.
当双向多对多关联中没有定义任何物理映射时, Hibernate根据以下规则生成相应的值关联表名: :主表表名+_下划线+从表表名,关联到主表的外键名:从表用于关联的属性名+_下划线+主表中的主键列名.
关联到从表的外键名:主表用于关联的属性名+_下划线+从表的主键列名.
以上规则对于双向一对多关联同样有效.
@Entity
public class Store {
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public Set<Customer> getCustomers() {
...
}
}
@Entity
public class Customer {
@ManyToMany(mappedBy="customers")
public Set<Store> getStores() {
...
}
}
在上面这个例子中,Store_Customer作为联接表.
stores_id列是联接到Store表的外键,而customers_id列联接到Customer表.
也许你已经注意到了cascade属性接受的值为CascadeType数组.
在EJB3中的cascade的概念和Hibernate中的传播性持久化以及cascade操作非常类似,但是在语义上有细微的区别,支持的cascade类型也有点区别:
CascadeType.PERSIST: 如果一个实体是受管状态, 或者当persist()函数被调用时, 触发级联创建(create)操作CascadeType.MERGE: 如果一个实体是受管状态, 或者当merge()函数被调用时, 触发级联合并(merge)操作 CascadeType.REMOVE: 当delete()函数被调用时, 触发级联删除(remove)操作CascadeType.REFRESH: 当refresh()函数被调用时, 触发级联更新(refresh)操作CascadeType.ALL:
以上全部关于cascading, create/merge的语义请参考EJB3规范的6.3章节.
通过Hibernate你可以获得直接或者延迟获取关联实体的功能.
fetch参数可以设置为FetchType.LAZY 或者 FetchType.EAGER.
EAGER通过outer join select直接获取关联的对象,而LAZY(默认值)在第一次访问关联对象的时候才会触发相应的select操作.
EJBQL提供了fetch关键字,该关键字可以在进行特殊查询的时候覆盖默认值.
这对于提高性能来说非常有效,应该根据实际的用例来判断是否选择fetch关键字.
组合主键使用一个可嵌入的类作为主键表示,因此你需要使用@Id和@Embeddable两个注解.
还有一种方式是使用@EmbeddedId注解.注意所依赖的类必须实现serializable以及实现equals()/hashCode()方法.
你也可以如一章中描述的办法使用@IdClass注解.
@Entity
public class RegionalArticle implements Serializable {
@Id
public RegionalArticlePk getPk() { ... }
}
@Embeddable
public class RegionalArticlePk implements Serializable { ... }
或者
@Entity
public class RegionalArticle implements Serializable {
@EmbeddedId
public RegionalArticlePk getPk() { ... }
}
public class RegionalArticlePk implements Serializable { ... }
@Embeddable 注解默认继承了其所属实体的访问类型,除非显式使用了Hibernate的@AccessType注解(这个注解不是EJB3标准的一部分).
而@JoinColumns,即@JoinColumn数组,定义了关联的组合外键(如果不使用缺省值的话).显式指明referencedColumnNames是一个好的实践方式,否则,Hibernate认为你使用的列顺序和主键声明的顺序一致.
@Entity
public class Parent implements Serializable {
@Id
public ParentPk id;
public int age;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumns ({
@JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
@JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
@JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Set<Child> children; //unidirectional
...
}
@Entity
public class Child implements Serializable {
@Id @GeneratedValue
public Integer id;
@ManyToOne
@JoinColumns ({
@JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
@JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
@JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Parent parent; //unidirectional
}
@Embeddable
public class ParentPk implements Serializable {
String firstName;
String lastName;
...
}
注意上面的 referencedColumnName显式使用方式.
使用类一级的 @SecondaryTable 或 @SecondaryTables 注解可以实现单个实体到多个表的映射.
使用 @Column 或者 @JoinColumn 注解中的 table 参数可指定某个列所属的特定表.
@Entity
@Table(name="MainCat")
@SecondaryTables({
@SecondaryTable(name="Cat1", pkJoinColumns={
@PrimaryKeyJoinColumn(name="cat_id", referencedColumnName="id")
),
@SecondaryTable(name="Cat2", uniqueConstraints={@UniqueConstraint(columnNames={"storyPart2"})})
})
public class Cat implements Serializable {
private Integer id;
private String name;
private String storyPart1;
private String storyPart2;
@Id @GeneratedValue
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Column(table="Cat1")
public String getStoryPart1() {
return storyPart1;
}
@Column(table="Cat2")
public String getStoryPart2() {
return storyPart2;
}
在上面这个例子中,name保存在MainCat表中,storyPart1保存在Cat1表中,storyPart2保存在Cat2表中.
Cat1表通过外键cat_id和MainCat表关联,Cat2表通过id列和MainCat表关联(和MainCat的id列同名).
对storyPart2列还定义了唯一约束.
在JBoss EJB 3指南和Hibernate Annotations单元测试代码中还有更多的例子.
使用注解还可以映射EJBQL/HQL查询.
@NamedQuery 和@NamedQueries注解可使用在类和JPA XML文件中.
但是它们的定义在session factory/entity manager factory范围中是都可见的.
命名式查询通过它的名字和实际的查询字符串来定义.
<entity-mappings>
<named-query name="plane.getAll">
<query>select p from Plane p</query>
</named-query>
...
</entity-mappings>
...
@Entity
@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")
public class Night {
...
}
public class MyDao {
doStuff() {
Query q = s.getNamedQuery("night.moreRecentThan");
q.setDate( "date", aMonthAgo );
List results = q.list();
...
}
...
}
还可以通过定义 QueryHint 数组的hints属性为查询提供一些hint信息.
下面是目前可以使用的一些Hibernate hint:
hint
description
org.hibernate.cacheable
查询是否与二级缓存交互(默认值为false)
org.hibernate.cacheRegion
设置缓存区名称 (默认为otherwise)
org.hibernate.timeout
查询超时设定
org.hibernate.fetchSize
所获取的结果集(resultset)大小
org.hibernate.flushMode
本次查询所用的刷新模式
org.hibernate.cacheMode
本次查询所用的缓存模式
org.hibernate.readOnly
是否将本次查询所加载的实体设为只读(默认为false)
org.hibernate.comment
将查询注释添加入所生成的SQL
你还可以映射本地化查询(也就是普通SQL查询).
不过这需要你使用@SqlResultSetMapping注解来描述SQL的resultset的结构
(如果你打算定义多个结果集映射,可是使用@SqlResultSetMappings).
@SqlResultSetMapping和@NamedQuery,
@SqlResultSetMapping一样,可以定义在类和JPA XML文件中.
但是@SqlResultSetMapping的作用域为应用级.
下面我们会看到,@NamedNativeQuery 注解中 resultSetMapping参数值为@SqlResultSetMapping的名字.
结果集映射定义了通过本地化查询返回值和实体的映射.
该实体中的每一个字段都绑定到SQL结果集中的某个列上.
该实体的所有字段包括子类的所有字段以及关联实体的外键列都必须在SQL查询中有对应的定义.
如果实体中的属性和SQL查询中的列名相同,这种情况下可以不进行定义字段映射.
@NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, "
+ " night.night_date, area.id aid, night.area_id, area.name "
+ "from Night night, Area area where night.area_id = area.id", role="bold">resultSetMapping="joinMapping")
@SqlResultSetMapping(name="joinMapping", entities={
@EntityResult(entityClass=org.hibernate.test.annotations.query.Night.class, fields = {
@FieldResult(name="id", column="nid"),
@FieldResult(name="duration", column="night_duration"),
@FieldResult(name="date", column="night_date"),
@FieldResult(name="area", column="area_id"),
discriminatorColumn="disc"
}),
@EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = {
@FieldResult(name="id", column="aid"),
@FieldResult(name="name", column="name")
})
}
)
在上面这个例子中,名为night&area的查询和joinMapping结果集映射对应.
该映射返回两个实体,分别为Night和Area,其中每个属性都和一个列关联,列名通过查询获取.下面我们看一个隐式声明属性和列映射关系的例子.
@Entity
@SqlResultSetMapping(name="implicit", entities=@EntityResult(entityClass=org.hibernate.test.annotations.query.SpaceShip.class))
@NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultSetMapping="implicit")
public class SpaceShip {
private String name;
private String model;
private double speed;
@Id
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Column(name="model_txt")
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
}
在这个例子中,我们只需要定义结果集映射中的实体成员.
属性和列名之间的映射借助实体中包含映射信息来完成.
在这个例子中,model属性绑定到model_txt列.
如果和相关实体的关联设计到组合主键,那么应该使用@FieldResult注解来定义每个外键列.
@FieldResult的名字由以下几部分组成:
定义这种关系的属性名字+"."+主键名或主键列或主键属性.
@Entity
@SqlResultSetMapping(name="compositekey",
entities=@EntityResult(entityClass=org.hibernate.test.annotations.query.SpaceShip.class,
fields = {
@FieldResult(name="name", column = "name"),
@FieldResult(name="model", column = "model"),
@FieldResult(name="speed", column = "speed"),
@FieldResult(name="captain.firstname", column = "firstn"),
@FieldResult(name="captain.lastname", column = "lastn"),
@FieldResult(name="dimensions.length", column = "length"),
@FieldResult(name="dimensions.width", column = "width")
}),
columns = { @ColumnResult(name = "surface"),
@ColumnResult(name = "volume") } )
@NamedNativeQuery(name="compositekey",
query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip",
resultSetMapping="compositekey")
} )
public class SpaceShip {
private String name;
private String model;
private double speed;
private Captain captain;
private Dimensions dimensions;
@Id
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumns( {
@JoinColumn(name="fname", referencedColumnName = "firstname"),
@JoinColumn(name="lname", referencedColumnName = "lastname")
} )
public Captain getCaptain() {
return captain;
}
public void setCaptain(Captain captain) {
this.captain = captain;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public Dimensions getDimensions() {
return dimensions;
}
public void setDimensions(Dimensions dimensions) {
this.dimensions = dimensions;
}
}
@Entity
@IdClass(Identity.class)
public class Captain implements Serializable {
private String firstname;
private String lastname;
@Id
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
@Id
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
观察dimension属性你会发现Hibernate支持用"."符号来表示嵌入式对象.
EJB3实现不必支持这个特征,但是我们做到了:-)
如果查询返回的是单个实体,或者你打算使用系统默认的映射,这种情况下可以不使用resultSetMapping 而是使用resultClass属性:
@NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultClass=SpaceShip.class)
public class SpaceShip {
某些本地查询返回的是scalar值,例如报表查询.
你可以通过@ColumnResult将其映射到@SqlResultsetMapping上.
甚至还可以在同一个本地查询的结果中混合实体和scalar类型(不过这种情况比较少见).
@SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension"))
@NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar")
本地查询中还有另外一个hint属性:
org.hibernate.callable.
这个属性的布尔变量值表明这个查询是否是一个存储过程.
Hibernate 3.1 提供了多种附加的注解,这些注解可以与EJB3的实体混合/匹配使用.
他们被设计成EJB3注解的自然扩展.
为了强化EJB3的能力,Hibernate提供了与其自身特性相吻合的特殊注解.
org.hibernate.annotations包已包含了所有的这些注解扩展.
你可以在EJB3规范所能提供的能力之外,就Hibernate对实体所做的一些操作进行优化.
@org.hibernate.annotations.Entity
追加了可能需要的额外的元数据,而这些元数据超出了标准@Entity 中所定义的元数据.
mutable: 此实体是否为可变的
dynamicInsert: 用动态SQL新增
dynamicUpdate: 用动态SQL更新
selectBeforeUpdate: 指明Hibernate从不运行SQL UPDATE除非能确定对象的确已被修改
polymorphism: (指出)实体多态是PolymorphismType.IMPLICIT(默认)还是PolymorphismType.EXPLICIT
persister:允许对默认持久实现(persister implementation)的覆盖
optimisticLock: 乐观锁策略(OptimisticLockType.VERSION, OptimisticLockType.NONE, OptimisticLockType.DIRTY或OptimisticLockType.ALL)
@javax.persistence.Entity仍是必选的(mandatory),
@org.hibernate.annotations.Entity不是取代品.
以下是一些附加的Hibernate注解扩展:
@org.hibernate.annotations.BatchSize
允许你定义批量获取该实体的实例数量(如:@BatchSize(size=4)).
当加载一特定的实体时,Hibernate将加载在持久上下文中未经初始化的同类型实体,直至批量数量(上限).
@org.hibernate.annotations.Proxy
定义了实体的延迟属性.Lazy(默认为true)定义了类是否为延迟(加载).
proxyClassName是用来生成代理的接口(默认为该类本身).
@org.hibernate.annotations.Where
定义了当获取类实例时所用的SQL WHERE子句(该SQL WHERE子句为可选).
@org.hibernate.annotations.Check
定义了在DDL语句中定义的合法性检查约束(该约束为可选).
@OnDelete(action=OnDeleteAction.CASCADE)
定义于被连接的子类(joined subclass):在删除时使用SQL级连删除,而非通常的Hibernate删除机制.
@Table(name="tableName", indexes = {
@Index(name="index1", columnNames={"column1", "column2"} ) } )
在tableName表的列上创建定义好的索引.
该注解可以被应用于关键表或者是其他次要的表.
@Tables 注解允许你在不同的表上应用索引.
此注解预期在使用@javax.persistence.Table或@javax.persistence.SecondaryTable的地方中出现.
@org.hibernate.annotations.Table 是对@javax.persistence.Table的补充而不是它的替代品.特别是当你打算改变表名的默认值的时候,你必须使用@javax.persistence.Table,而不是@org.hibernate.annotations.Table.
@Entity
@BatchSize(size=5)
@org.hibernate.annotations.Entity(
selectBeforeUpdate = true,
dynamicInsert = true, dynamicUpdate = true,
optimisticLock = OptimisticLockType.ALL,
polymorphism = PolymorphismType.EXPLICIT)
@Where(clause="1=1")
@org.hibernate.annotations.Table(name="Forest", indexes = { @Index(name="idx", columnNames = { "name", "length" } ) } )
public class Forest { ... }@Entity
@Inheritance(
strategy=InheritanceType.JOINED
)
public class Vegetable { ... }
@Entity
@OnDelete(action=OnDeleteAction.CASCADE)
public class Carrot extends Vegetable { ... }
@org.hibernate.annotations.GenericGenerator
允许你定义一个Hibernate特定的id生成器.
@Id @GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
public String getId() {
@Id @GeneratedValue(generator="hibseq")
@GenericGenerator(name="hibseq", strategy = "seqhilo",
parameters = {
@Parameter(name="max_lo", value = "5"),
@Parameter(name="sequence", value="heybabyhey")
}
)
public Integer getId() {
strategy可以是Hibernate3生成器策略的简称,或者是一个IdentifierGenerator实现的(带包路径的)全限定类名.你可以通过parameters属性增加一些参数.和标准的对比,@GenericGenerator是可用于包一级的注解, 使之成为应用级的生成器(就象在JPA XML文件里面那样).
@GenericGenerator(name="hibseq", strategy = "seqhilo",
parameters = {
@Parameter(name="max_lo", value = "5"),
@Parameter(name="sequence", value="heybabyhey")
}
)
package org.hibernate.test.model
访问类型是根据@Id或@EmbeddedId在实体继承层次中所处的位置推演而得的.子实体(Sub-entities),内嵌对象和被映射的父类均继承了根实体(root entity)的访问类型.
在Hibernate中,你可以把访问类型覆盖成:
使用定制的访问类型策略优化类级或属性级的访问类型为支持这种行为,Hibernate引入了@AccessType注解.你可以对以下元素定义访问类型:
实体
父类
可内嵌的对象
属性
被注解元素的访问类型会被覆盖,若覆盖是在类一级上,则所有的属性继承访问类型.
对于根实体,其访问类型会被认为是整个继承层次中的缺省设置(可在类或属性一级覆盖).
若访问类型被标以"property",则Hibernate会扫描getter方法的注解,若访问类型被标以"field",
则扫描字段的注解.否则,扫描标为@Id或@embeddedId的元素.
你可以覆盖某个属性(property)的访问类型,但是受注解的元素将不受影响:
例如一个具有field访问类型的实体,(我们)可以将某个字段标注为 @AccessType("property"),则该字段的访问类型随之将成为property,但是其他字段上依然需要携带注解.
若父类或可内嵌的对象没有被注解,则使用根实体的访问类型(即使已经在非直系父类或可内嵌对象上定义了访问类型).
此时俄罗斯套娃(Russian doll)原理就不再适用.(译注:俄罗斯套娃(матрёшка或 матрешка)是俄罗斯特产木制玩具,一般由多个一样图案的空心木娃娃一个套一个组成,最多可达十多个,通常为圆柱形,底部平坦可以直立.)
@Entity
public class Person implements Serializable {
@Id @GeneratedValue //access type field
Integer id;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "iso2", column = @Column(name = "bornIso2")),
@AttributeOverride(name = "name", column = @Column(name = "bornCountryName"))
})
Country bornIn;
}
@Embeddable
@AccessType("property") //override access type for all properties in Country
public class Country implements Serializable {
private String iso2;
private String name;
public String getIso2() {
return iso2;
}
public void setIso2(String iso2) {
this.iso2 = iso2;
}
@Column(name = "countryName")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
现在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 = {@UniqueConstraint(columnNames={"month", "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上进行了小小的提升,允许在同一实体中使用两个同类型的嵌入对象而无须额外的声明.
如果某属性没有注解,该属性将遵守下面的规则:
如果属性为单一类型,则映射为@Basic
否则,如果属性对应的类型定义了@Embeddable注解,则映射为@Embedded
否则,如果属性对应的类型实现了Serializable,
则属性被映射为@Basic并在一个列中保存该对象的serialized版本
否则,如果该属性的类型为java.sql.Clob 或 java.sql.Blob,则作为@Lob并映射到适当的LobType.
xreflabel="Mapping identifier properties">
使用@Id注解可以将实体bean中的某个属性定义为标识符(identifier).
该属性的值可以通过应用自身进行设置,也可以通过Hiberante生成(推荐).
使用 @GeneratedValue注解可以定义该标识符的生成策略:
AUTO - 可以是identity column类型,或者sequence类型或者table类型,取决于不同的底层数据库.
TABLE - 使用表保存id值
IDENTITY - identity column
SEQUENCE - sequence
和EJB3规范相比,Hibernate提供了更多的id生成器.详情请查阅 .
下面的例子展示了使用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).
类一级的生成器在外部是不可见的,而且类一级的生成器可以覆盖应用级的生成器.
应用级的生成器则定义在XML级(请参阅):
<table-generator name="EMP_GEN"
table="GENERATOR_TABLE"
pk-column-name="key"
value-column-name="hi"
pk-column-value="EMP"
allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.TableGenerator(
name="EMP_GEN",
table="GENERATOR_TABLE",
pkColumnName = "key",
valueColumnName = "hi"
pkColumnValue="EMP",
allocationSize=20
)
<sequence-generator name="SEQ_GEN"
sequence-name="my_sequence"
allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.SequenceGenerator(
name="SEQ_GEN",
sequenceName="my_sequence",
allocationSize=20
)
如果JPA XML(如META-INF/orm.xml)用于定义生成器, 那么该文件中定义的 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生成器,
其使用名为my_sequence的sequence.
该hilo算法基于sequence,该sequence分配的大小为20.
注意,现在这个版本还不能处理sequence生成器的initialValue属性.
默认分配的大小为50,因此如果你打算使用sequence,并且希望每次都重新获取新的值,务必将
分配的大小设置为1.
EJB3.0规范已经不再支持Package级别的定义. 但是你仍然可以在包上使用
@GenericGenerator注解(详情请参考 linkend="entity-hibspec-identifier" />).
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;
}
EJB3支持三种类型的继承映射:
每个类一张表(Table per class)策略: 在Hibernate中对应<union-class>元素:
每个类层次结构一张表(Single table per class hierarchy)策略:在Hibernate中对应<subclass>元素
连接的子类(Joined subclasses)策略:在Hibernate中对应 <joined-subclass>元素
你可以用 @Inheritance注解来定义所选择的策略.
这个注解需要在每个类层次结构(class hierarchy) 最顶端的实体类上使用.
目前还不支持在接口上进行注解.
这种策略有很多缺点(例如:多态查询和关联),EJB3规范, Hibernate参考手册,
Hibernate in Action,以及其他许多地方都对此进行了描述和解释.
Hibernate使用SQL UNION查询来实现这种策略.
通常使用场合是在一个继承层次结构的顶端:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable {
这种策略支持双向的一对多关联.
这里不支持IDENTITY生成器策略,因为id必须在多个表间共享.
当然,一旦使用这种策略就意味着你不能使用
AUTO 生成器和IDENTITY生成器.
整个继承层次结构中的父类和子类的所有属性都映射到同一个表中,他们的实例通过一个辨别符(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 注解只能用于实体层次结构的顶端.
当每个子类映射到一个表时, @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进行关联.
有时候通过一个(技术上或业务上)父类共享一些公共属性是很有用的,同时还不用将该父类作为映射的实体(也就是该实体没有对应的表).
这个时候你需要使用@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.
使用@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的属性值.
你必须明确定义关联表名和关联列名.
在实体属性一级使用@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}, role="bold">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;
}
...
}
你可以对 Collection ,List(指有序列表, 而不是索引列表),Map和Set这几种类型进行映射.
EJB3规范定义了怎么样使用@javax.persistence.OrderBy
注解来对有序列表进行映射:
该注解接受的参数格式:用逗号隔开的(目标实体)属性名及排序指令,如firstname asc, age desc,如果该参数为空,则默认以id对该集合进行排序.
如果某个集合在数据库中对应一个关联表(association table)的话,你不能在这个集合属性上面使用@OrderBy注解.
对于这种情况的处理方法,请参考.
EJB3 允许你利用目标实体的一个属性作为Map的key,
这个属性可以用@MapKey(name="myProperty")来声明.
如果使用@MapKey注解的时候不提供属性名,
系统默认使用目标实体的主键.
map的key使用和属性相同的列:不需要为map key定义专用的列,因为map key实际上就表达了一个目标属性.
注意一旦加载,key不再和属性保持同步,也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新
(请参考).
很多人被<map>和@MapKey弄糊涂了.
其他它们有两点区别.@MapKey目前还有一些限制,详情请查看论坛或者我们的JIRA缺陷系统.
注意一旦加载,key不再和属性保持同步,也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新.
(Hibernate 3中Map支持的方式在当前的发布版中还未得到支持). Hibernate将集合分以下几类.
语义
Java实现类
注解
Bag 语义
java.util.List, java.util.Collection
@org.hibernate.annotations.CollectionOfElements 或
@OneToMany 或 @ManyToMany
带主键的Bag语义(没有普通Bag语义的限制)
java.util.List, java.util.Collection
(@org.hibernate.annotations.CollectionOfElements 或
@OneToMany 或 @ManyToMany) 和 @CollectionId
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对此提供了支持
(详情参考 ).
@Entity public class City {
@OneToMany(mappedBy="city")
@OrderBy("streetName")
public List<Street> 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<String, Version> 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.
在属性级使用 @OneToMany注解可定义一对多关联.一对多关联可以是双向关联.
在EJB3规范中多对一这端几乎总是双向关联中的主体(owner)端,而一对多这端的关联注解为@OneToMany( mappedBy=...
)
@Entity
public class Troop {
@OneToMany(mappedBy="troop")
public Set<Soldier> 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<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
通过在被拥有的实体端(owned entity)增加一个外键列来实现一对多单向关联是很少见的,也是不推荐的.
我们强烈建议通过一个联接表(join table)来实现这种关联(下一节会对此进行解释).
可以通过@JoinColumn注解来描述这种单向关联关系.
@Entity
public class Customer implements Serializable {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
public Set<Ticket> getTickets() {
...
}
@Entity
public class Ticket implements Serializable {
... //no bidir
}
Customer 通过CUST_ID列和Ticket 建立了单向关联关系.
通过联接表处理单向一对多关联是首选方式.这种关联通过@JoinTable注解来进行描述.
@Entity
public class Trainer {
@OneToMany
@JoinTable(
name="TrainedMonkeys",
joinColumns = { @JoinColumn( name="trainer_id") },
inverseJoinColumns = @JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
@Entity
public class Monkey {
... //no bidir
}
上面这个例子中,Trainer通过TrainedMonkeys表和 Monkey 建立了单向关联.
其中外键trainer_id关联到Trainer(joinColumns), 而外键monkey_id关联到 Monkey
(inversejoinColumns).
通过联接表来建立单向一对多关联不需要描述任何物理映射.
表名由以下三个部分组成:主表(owner table)表名+下划线+从表(the other side table)表名.
指向主表的外键名:主表表名+下划线+主表主键列名
指向从表的外键名:主表所对应实体的属性名+下划线+从表主键列名
指向从表的外键定义为唯一约束,用来表示一对多的关联关系.
@Entity
public class Trainer {
@OneToMany
public Set<Tiger> getTrainedTigers() {
...
}
@Entity
public class Tiger {
... //no bidir
}
上面这个例子中,Trainer和Tiger通过联接表 Trainer_Tiger建立单向关联关系,其中外键trainer_id关联到Trainer(主表表名, _(下划线), trainer id),而外键trainedTigers_id关联到Tiger(属性名称, _(下划线), Tiger表的主键列名).
你可以通过@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参数,该参数包含了主体端的属性名,这样就绑定双方的关系.
和其他许多注解一样,在多对多关联中很多值是自动生成.
当双向多对多关联中没有定义任何物理映射时,Hibernate根据以下规则生成相应的值.
关联表名:主表表名+_下划线+从表表名,关联到主表的外键名:主表名+_下划线+主表中的主键列名.
关联到从表的外键名:主表中用于关联的属性名+_下划线+从表的主键列名.
以上规则对于双向一对多关联同样有效.
@Entity
public class Store {
@ManyToMany(cascade = CascadeType.PERSIST)
public Set<City> getImplantedIn() {
...
}
}
@Entity
public class City {
... //no bidirectional relationship
}
上面这个例子中,Store_City作为联接表.
Store_id列是联接到Store表的外键.
而implantedIn_id列则联接到City表.
当双向多对多关联中没有定义任何物理映射时, Hibernate根据以下规则生成相应的值关联表名: :主表表名+_下划线+从表表名,关联到主表的外键名:从表用于关联的属性名+_下划线+主表中的主键列名.
关联到从表的外键名:主表用于关联的属性名+_下划线+从表的主键列名.
以上规则对于双向一对多关联同样有效.
@Entity
public class Store {
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public Set<Customer> getCustomers() {
...
}
}
@Entity
public class Customer {
@ManyToMany(mappedBy="customers")
public Set<Store> getStores() {
...
}
}
在上面这个例子中,Store_Customer作为联接表.
stores_id列是联接到Store表的外键,而customers_id列联接到Customer表.
也许你已经注意到了cascade属性接受的值为CascadeType数组.
在EJB3中的cascade的概念和Hibernate中的传播性持久化以及cascade操作非常类似,但是在语义上有细微的区别,支持的cascade类型也有点区别:
CascadeType.PERSIST: 如果一个实体是受管状态, 或者当persist()函数被调用时, 触发级联创建(create)操作CascadeType.MERGE: 如果一个实体是受管状态, 或者当merge()函数被调用时, 触发级联合并(merge)操作 CascadeType.REMOVE: 当delete()函数被调用时, 触发级联删除(remove)操作CascadeType.REFRESH: 当refresh()函数被调用时, 触发级联更新(refresh)操作CascadeType.ALL:
以上全部关于cascading, create/merge的语义请参考EJB3规范的6.3章节.
通过Hibernate你可以获得直接或者延迟获取关联实体的功能.
fetch参数可以设置为FetchType.LAZY 或者 FetchType.EAGER.
EAGER通过outer join select直接获取关联的对象,而LAZY(默认值)在第一次访问关联对象的时候才会触发相应的select操作.
EJBQL提供了fetch关键字,该关键字可以在进行特殊查询的时候覆盖默认值.
这对于提高性能来说非常有效,应该根据实际的用例来判断是否选择fetch关键字.
组合主键使用一个可嵌入的类作为主键表示,因此你需要使用@Id和@Embeddable两个注解.
还有一种方式是使用@EmbeddedId注解.注意所依赖的类必须实现serializable以及实现equals()/hashCode()方法.
你也可以如一章中描述的办法使用@IdClass注解.
@Entity
public class RegionalArticle implements Serializable {
@Id
public RegionalArticlePk getPk() { ... }
}
@Embeddable
public class RegionalArticlePk implements Serializable { ... }
或者
@Entity
public class RegionalArticle implements Serializable {
@EmbeddedId
public RegionalArticlePk getPk() { ... }
}
public class RegionalArticlePk implements Serializable { ... }
@Embeddable 注解默认继承了其所属实体的访问类型,除非显式使用了Hibernate的@AccessType注解(这个注解不是EJB3标准的一部分).
而@JoinColumns,即@JoinColumn数组,定义了关联的组合外键(如果不使用缺省值的话).显式指明referencedColumnNames是一个好的实践方式,否则,Hibernate认为你使用的列顺序和主键声明的顺序一致.
@Entity
public class Parent implements Serializable {
@Id
public ParentPk id;
public int age;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumns ({
@JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
@JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
@JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Set<Child> children; //unidirectional
...
}
@Entity
public class Child implements Serializable {
@Id @GeneratedValue
public Integer id;
@ManyToOne
@JoinColumns ({
@JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
@JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
@JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Parent parent; //unidirectional
}
@Embeddable
public class ParentPk implements Serializable {
String firstName;
String lastName;
...
}
注意上面的 referencedColumnName显式使用方式.
使用类一级的 @SecondaryTable 或 @SecondaryTables 注解可以实现单个实体到多个表的映射.
使用 @Column 或者 @JoinColumn 注解中的 table 参数可指定某个列所属的特定表.
@Entity
@Table(name="MainCat")
@SecondaryTables({
@SecondaryTable(name="Cat1", pkJoinColumns={
@PrimaryKeyJoinColumn(name="cat_id", referencedColumnName="id")
),
@SecondaryTable(name="Cat2", uniqueConstraints={@UniqueConstraint(columnNames={"storyPart2"})})
})
public class Cat implements Serializable {
private Integer id;
private String name;
private String storyPart1;
private String storyPart2;
@Id @GeneratedValue
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Column(table="Cat1")
public String getStoryPart1() {
return storyPart1;
}
@Column(table="Cat2")
public String getStoryPart2() {
return storyPart2;
}
在上面这个例子中,name保存在MainCat表中,storyPart1保存在Cat1表中,storyPart2保存在Cat2表中.
Cat1表通过外键cat_id和MainCat表关联,Cat2表通过id列和MainCat表关联(和MainCat的id列同名).
对storyPart2列还定义了唯一约束.
在JBoss EJB 3指南和Hibernate Annotations单元测试代码中还有更多的例子.
使用注解还可以映射EJBQL/HQL查询.
@NamedQuery 和@NamedQueries注解可使用在类和JPA XML文件中.
但是它们的定义在session factory/entity manager factory范围中是都可见的.
命名式查询通过它的名字和实际的查询字符串来定义.
<entity-mappings>
<named-query name="plane.getAll">
<query>select p from Plane p</query>
</named-query>
...
</entity-mappings>
...
@Entity
@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")
public class Night {
...
}
public class MyDao {
doStuff() {
Query q = s.getNamedQuery("night.moreRecentThan");
q.setDate( "date", aMonthAgo );
List results = q.list();
...
}
...
}
还可以通过定义 QueryHint 数组的hints属性为查询提供一些hint信息.
下面是目前可以使用的一些Hibernate hint:
hint
description
org.hibernate.cacheable
查询是否与二级缓存交互(默认值为false)
org.hibernate.cacheRegion
设置缓存区名称 (默认为otherwise)
org.hibernate.timeout
查询超时设定
org.hibernate.fetchSize
所获取的结果集(resultset)大小
org.hibernate.flushMode
本次查询所用的刷新模式
org.hibernate.cacheMode
本次查询所用的缓存模式
org.hibernate.readOnly
是否将本次查询所加载的实体设为只读(默认为false)
org.hibernate.comment
将查询注释添加入所生成的SQL
你还可以映射本地化查询(也就是普通SQL查询).
不过这需要你使用@SqlResultSetMapping注解来描述SQL的resultset的结构
(如果你打算定义多个结果集映射,可是使用@SqlResultSetMappings).
@SqlResultSetMapping和@NamedQuery,
@SqlResultSetMapping一样,可以定义在类和JPA XML文件中.
但是@SqlResultSetMapping的作用域为应用级.
下面我们会看到,@NamedNativeQuery 注解中 resultSetMapping参数值为@SqlResultSetMapping的名字.
结果集映射定义了通过本地化查询返回值和实体的映射.
该实体中的每一个字段都绑定到SQL结果集中的某个列上.
该实体的所有字段包括子类的所有字段以及关联实体的外键列都必须在SQL查询中有对应的定义.
如果实体中的属性和SQL查询中的列名相同,这种情况下可以不进行定义字段映射.
@NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, "
+ " night.night_date, area.id aid, night.area_id, area.name "
+ "from Night night, Area area where night.area_id = area.id", role="bold">resultSetMapping="joinMapping")
@SqlResultSetMapping(name="joinMapping", entities={
@EntityResult(entityClass=org.hibernate.test.annotations.query.Night.class, fields = {
@FieldResult(name="id", column="nid"),
@FieldResult(name="duration", column="night_duration"),
@FieldResult(name="date", column="night_date"),
@FieldResult(name="area", column="area_id"),
discriminatorColumn="disc"
}),
@EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = {
@FieldResult(name="id", column="aid"),
@FieldResult(name="name", column="name")
})
}
)
在上面这个例子中,名为night&area的查询和joinMapping结果集映射对应.
该映射返回两个实体,分别为Night和Area,其中每个属性都和一个列关联,列名通过查询获取.下面我们看一个隐式声明属性和列映射关系的例子.
@Entity
@SqlResultSetMapping(name="implicit", entities=@EntityResult(entityClass=org.hibernate.test.annotations.query.SpaceShip.class))
@NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultSetMapping="implicit")
public class SpaceShip {
private String name;
private String model;
private double speed;
@Id
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Column(name="model_txt")
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
}
在这个例子中,我们只需要定义结果集映射中的实体成员.
属性和列名之间的映射借助实体中包含映射信息来完成.
在这个例子中,model属性绑定到model_txt列.
如果和相关实体的关联设计到组合主键,那么应该使用@FieldResult注解来定义每个外键列.
@FieldResult的名字由以下几部分组成:
定义这种关系的属性名字+"."+主键名或主键列或主键属性.
@Entity
@SqlResultSetMapping(name="compositekey",
entities=@EntityResult(entityClass=org.hibernate.test.annotations.query.SpaceShip.class,
fields = {
@FieldResult(name="name", column = "name"),
@FieldResult(name="model", column = "model"),
@FieldResult(name="speed", column = "speed"),
@FieldResult(name="captain.firstname", column = "firstn"),
@FieldResult(name="captain.lastname", column = "lastn"),
@FieldResult(name="dimensions.length", column = "length"),
@FieldResult(name="dimensions.width", column = "width")
}),
columns = { @ColumnResult(name = "surface"),
@ColumnResult(name = "volume") } )
@NamedNativeQuery(name="compositekey",
query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip",
resultSetMapping="compositekey")
} )
public class SpaceShip {
private String name;
private String model;
private double speed;
private Captain captain;
private Dimensions dimensions;
@Id
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumns( {
@JoinColumn(name="fname", referencedColumnName = "firstname"),
@JoinColumn(name="lname", referencedColumnName = "lastname")
} )
public Captain getCaptain() {
return captain;
}
public void setCaptain(Captain captain) {
this.captain = captain;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public Dimensions getDimensions() {
return dimensions;
}
public void setDimensions(Dimensions dimensions) {
this.dimensions = dimensions;
}
}
@Entity
@IdClass(Identity.class)
public class Captain implements Serializable {
private String firstname;
private String lastname;
@Id
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
@Id
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
观察dimension属性你会发现Hibernate支持用"."符号来表示嵌入式对象.
EJB3实现不必支持这个特征,但是我们做到了:-)
如果查询返回的是单个实体,或者你打算使用系统默认的映射,这种情况下可以不使用resultSetMapping 而是使用resultClass属性:
@NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultClass=SpaceShip.class)
public class SpaceShip {
某些本地查询返回的是scalar值,例如报表查询.
你可以通过@ColumnResult将其映射到@SqlResultsetMapping上.
甚至还可以在同一个本地查询的结果中混合实体和scalar类型(不过这种情况比较少见).
@SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension"))
@NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar")
本地查询中还有另外一个hint属性:
org.hibernate.callable.
这个属性的布尔变量值表明这个查询是否是一个存储过程.
Hibernate 3.1 提供了多种附加的注解,这些注解可以与EJB3的实体混合/匹配使用.
他们被设计成EJB3注解的自然扩展.
为了强化EJB3的能力,Hibernate提供了与其自身特性相吻合的特殊注解.
org.hibernate.annotations包已包含了所有的这些注解扩展.
你可以在EJB3规范所能提供的能力之外,就Hibernate对实体所做的一些操作进行优化.
@org.hibernate.annotations.Entity
追加了可能需要的额外的元数据,而这些元数据超出了标准@Entity 中所定义的元数据.
mutable: 此实体是否为可变的
dynamicInsert: 用动态SQL新增
dynamicUpdate: 用动态SQL更新
selectBeforeUpdate: 指明Hibernate从不运行SQL UPDATE除非能确定对象的确已被修改
polymorphism: (指出)实体多态是PolymorphismType.IMPLICIT(默认)还是PolymorphismType.EXPLICIT
persister:允许对默认持久实现(persister implementation)的覆盖
optimisticLock: 乐观锁策略(OptimisticLockType.VERSION, OptimisticLockType.NONE, OptimisticLockType.DIRTY或OptimisticLockType.ALL)
@javax.persistence.Entity仍是必选的(mandatory),
@org.hibernate.annotations.Entity不是取代品.
以下是一些附加的Hibernate注解扩展:
@org.hibernate.annotations.BatchSize
允许你定义批量获取该实体的实例数量(如:@BatchSize(size=4)).
当加载一特定的实体时,Hibernate将加载在持久上下文中未经初始化的同类型实体,直至批量数量(上限).
@org.hibernate.annotations.Proxy
定义了实体的延迟属性.Lazy(默认为true)定义了类是否为延迟(加载).
proxyClassName是用来生成代理的接口(默认为该类本身).
@org.hibernate.annotations.Where
定义了当获取类实例时所用的SQL WHERE子句(该SQL WHERE子句为可选).
@org.hibernate.annotations.Check
定义了在DDL语句中定义的合法性检查约束(该约束为可选).
@OnDelete(action=OnDeleteAction.CASCADE)
定义于被连接的子类(joined subclass):在删除时使用SQL级连删除,而非通常的Hibernate删除机制.
@Table(name="tableName", indexes = {
@Index(name="index1", columnNames={"column1", "column2"} ) } )
在tableName表的列上创建定义好的索引.
该注解可以被应用于关键表或者是其他次要的表.
@Tables 注解允许你在不同的表上应用索引.
此注解预期在使用@javax.persistence.Table或@javax.persistence.SecondaryTable的地方中出现.
@org.hibernate.annotations.Table 是对@javax.persistence.Table的补充而不是它的替代品.特别是当你打算改变表名的默认值的时候,你必须使用@javax.persistence.Table,而不是@org.hibernate.annotations.Table.
@Entity
@BatchSize(size=5)
@org.hibernate.annotations.Entity(
selectBeforeUpdate = true,
dynamicInsert = true, dynamicUpdate = true,
optimisticLock = OptimisticLockType.ALL,
polymorphism = PolymorphismType.EXPLICIT)
@Where(clause="1=1")
@org.hibernate.annotations.Table(name="Forest", indexes = { @Index(name="idx", columnNames = { "name", "length" } ) } )
public class Forest { ... }@Entity
@Inheritance(
strategy=InheritanceType.JOINED
)
public class Vegetable { ... }
@Entity
@OnDelete(action=OnDeleteAction.CASCADE)
public class Carrot extends Vegetable { ... }
@org.hibernate.annotations.GenericGenerator
允许你定义一个Hibernate特定的id生成器.
@Id @GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
public String getId() {
@Id @GeneratedValue(generator="hibseq")
@GenericGenerator(name="hibseq", strategy = "seqhilo",
parameters = {
@Parameter(name="max_lo", value = "5"),
@Parameter(name="sequence", value="heybabyhey")
}
)
public Integer getId() {
strategy可以是Hibernate3生成器策略的简称,或者是一个IdentifierGenerator实现的(带包路径的)全限定类名.你可以通过parameters属性增加一些参数.和标准的对比,@GenericGenerator是可用于包一级的注解, 使之成为应用级的生成器(就象在JPA XML文件里面那样).
@GenericGenerator(name="hibseq", strategy = "seqhilo",
parameters = {
@Parameter(name="max_lo", value = "5"),
@Parameter(name="sequence", value="heybabyhey")
}
)
package org.hibernate.test.model
访问类型是根据@Id或@EmbeddedId在实体继承层次中所处的位置推演而得的.子实体(Sub-entities),内嵌对象和被映射的父类均继承了根实体(root entity)的访问类型.
在Hibernate中,你可以把访问类型覆盖成:
使用定制的访问类型策略优化类级或属性级的访问类型为支持这种行为,Hibernate引入了@AccessType注解.你可以对以下元素定义访问类型:
实体
父类
可内嵌的对象
属性
被注解元素的访问类型会被覆盖,若覆盖是在类一级上,则所有的属性继承访问类型.
对于根实体,其访问类型会被认为是整个继承层次中的缺省设置(可在类或属性一级覆盖).
若访问类型被标以"property",则Hibernate会扫描getter方法的注解,若访问类型被标以"field",
则扫描字段的注解.否则,扫描标为@Id或@embeddedId的元素.
你可以覆盖某个属性(property)的访问类型,但是受注解的元素将不受影响:
例如一个具有field访问类型的实体,(我们)可以将某个字段标注为 @AccessType("property"),则该字段的访问类型随之将成为property,但是其他字段上依然需要携带注解.
若父类或可内嵌的对象没有被注解,则使用根实体的访问类型(即使已经在非直系父类或可内嵌对象上定义了访问类型).
此时俄罗斯套娃(Russian doll)原理就不再适用.(译注:俄罗斯套娃(матрёшка或 матрешка)是俄罗斯特产木制玩具,一般由多个一样图案的空心木娃娃一个套一个组成,最多可达十多个,通常为圆柱形,底部平坦可以直立.)
@Entity
public class Person implements Serializable {
@Id @GeneratedValue //access type field
Integer id;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "iso2", column = @Column(name = "bornIso2")),
@AttributeOverride(name = "name", column = @Column(name = "bornCountryName"))
})
Country bornIn;
}
@Embeddable
@AccessType("property") //override access type for all properties in Country
public class Country implements Serializable {
private String iso2;
private String name;
public String getIso2() {
return iso2;
}
public void setIso2(String iso2) {
this.iso2 = iso2;
}
@Column(name = "countryName")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
发表评论
-
ConcurrentHashMap与CopyOnWriteArrayList比较。
2010-12-14 10:24 1711ConcurrentHashMap ConcurrentH ... -
Servlet与Struts action线程安全问题分析
2010-03-24 19:45 1298Servlet/JSP技术和ASP、PHP等相比,由于其多线程 ... -
JVM调优
2010-03-24 19:37 1146原文: http://blog.csdn.net/tyrone ... -
HttpURLConnection VS HttpClient性能测试
2010-03-16 14:19 9010版本: HttpURLConnection jdk1.6;H ... -
jdk 1.6新特性
2010-02-03 09:27 36832006 年底,Sun 公司发布了 Java Standard ... -
REST是什么
2009-11-06 18:27 1223REST架构风格是全新的针对Web应用的开发风格,是当今世界最 ... -
jira企业版linux下安装和破解
2009-08-07 13:42 57431、下载地址:http://www.atlassian.com ... -
log4j输出到文件和数据库
2009-05-20 21:07 4655官方API地址:http://logging.apache.o ... -
Log4j详细配置
2009-05-19 10:51 929一、前言: log4j 是一个开放源码项目,是广泛 ... -
ANT-build.xml文件详解
2009-03-12 13:54 1569Ant的概念 可能有些读者并不连接什么是Ant以及 ... -
tomcat配置说明和内存扩容
2009-03-05 14:37 24871. 如何加大tomcat连接数 在tomcat配置文件ser ... -
ehCache在acegi中的应用
2009-03-03 16:56 2918EhCache一般用途如下:Hibernate缓存,DA ... -
特殊字符转义
2009-02-24 10:52 1035请看附件图片。 -
Eclipse快捷键大全
2009-02-16 15:06 881Eclipse中自定义设置快捷键: Window --> ... -
Mime类型收集
2009-02-12 15:41 1024网方网站:http://www.mimetype.org/ ... -
lukeall-0.8.1.jar Lucene索引查看工具
2008-12-30 12:54 7180lukeall-0.8.1.jar 在windows下双击, ... -
EJB3.0-JPA实体的注解规范以及Hibernate特有的扩展(下)
2008-12-29 17:13 8576有时候,你想让数据库,而非JVM,来替你完成一些计算,也 ... -
常见异常解析
2008-12-29 11:36 11731. java.lang.nullpointerexce ... -
org.apache.commons.lang.builder
2008-12-26 10:10 2370在org.apache.commons.lang.b ... -
java(Web)中相对路径,绝对路径问题
2008-11-28 11:51 18431.基本概念的理解 绝对路径:绝对路径就是你的主页上的文件或 ...
相关推荐
### EJB3.0-JPA实体的注解规范以及Hibernate特有的扩展 #### 一、概述 EJB3.0引入了一种新的编程模型,它基于Java Persistence API (JPA),使得开发人员能够更加轻松地创建企业级应用。JPA允许使用POJO(Plain Old...
EJB3.0实体的注解规范主要涵盖了Java企业版(Java EE)中实体Bean的定义方式,这一规范也适用于Java Persistence API (JPA),并且整合了Hibernate的特有扩展。在EJB3.0中,实体Bean被设计为简单的Plain Old Java ...
综上所述,ejb3.0规范PDF包含了大量的信息,从配置简化到实体Bean的JPA集成,再到会话Bean、消息驱动Bean的使用,以及依赖注入、接口驱动编程、事务管理和安全性等多个方面,为Java EE开发者提供了全面的指导。...
【EJB学习大全】是针对企业级Java应用开发的一个重要主题,主要涵盖了EJB3.0、JPA教程以及实战EJB的相关知识。EJB,全称为Enterprise JavaBeans,是Java平台上的一个核心组件,主要用于构建分布式、面向服务的企业级...
在"ejb3.0入门经典教程-source"这个压缩包中,包含了书中各个章节的示例代码,覆盖了EJB 3.0的各个方面,例如实体Bean的创建、会话Bean的使用、事务管理、安全性设置以及JPA的持久化操作等。这些源码对于初学者来说...
在EJB 3.0中,开发者可以使用注解(Annotations)来声明实体、会话和消息驱动bean,以及它们的相关行为。例如,`@Entity`用于标记实体bean,`@Stateless`和`@Stateful`分别表示无状态和有状态会话bean,`@Message...
### JPA学习笔记-EJB-02JPA属性注解 #### 一、引言 在上一篇文章中,我们简要介绍了Java Persistence API (JPA)的基础知识,包括它的基本部署和操作流程,从而让我们对JPA有了初步的认识。本文将继续深入探讨JPA的...
- **实体Bean(Entity Beans)**:EJB 3.0中的实体Bean不再需要编写复杂的EJB接口和实现类,而是通过JPA(Java Persistence API)进行持久化管理,可以利用注解(Annotations)直接在实体类上声明数据存储的相关属性...
EJB 3.0引入了Java Persistence API(JPA),使得实体bean的管理更加简单,可以与ORM(对象关系映射)框架如Hibernate集成。 **3. EJB 3.0新特性** - **注解驱动**:例如,`@Entity`用于标记实体bean,`@EJB`用于...
1. 注解驱动:EJB 3.0允许开发者通过注解来声明组件的元数据,如实体Bean的@Entity,会话Bean的@Stateless或@Stateful,以及服务接口的@Service等。这种方式避免了EJB 2.x中的XML配置文件,使得代码更加简洁明了。 ...
3. **容器管理的依赖注入(Dependency Injection)**:EJB 3.0引入了JSR 250规范,允许通过@EJB、@Resource、@PersistenceContext和@PersistenceUnit等注解实现依赖注入,简化了组件间的协作。 4. **查询语言(JPQL...
同时,EJB 3.0支持JPA(Java Persistence API),允许开发者使用Hibernate、TopLink等持久化框架。 2. **会话Bean(Session Beans)**:会话Bean用于处理业务逻辑。EJB 3.0允许开发者使用无状态会话Bean(`@...
这些jar可能包含了实体bean的实现库,如`hibernate-entitymanager.jar`(如果使用Hibernate作为JPA实现)。 3. **Session Beans**:会话bean(Session Bean)是EJB3.0中的业务逻辑组件,它们负责处理应用逻辑。相关...
EJB3.0是EJB规范的一个重要版本,它在EJB2.1的基础上进行了大量简化和改进,使得开发更加便捷,更符合现代软件开发的需求。 EJB3.0引入了以下几个关键概念和技术点: 1. **注解驱动**:EJB3.0引入了注解...
EJB 3.0的持久化API实际上是基于JPA规范的实现。Entity Beans的概念被重新定义,不再需要EJB容器管理的事务和生命周期,而是更接近于POJO(Plain Old Java Object)。JPA提供了一个标准接口来操作持久化对象,兼容...
总结来说,`ejb3-persistence.jar`和`hibernate-annotations.jar`是Java企业级开发中的重要组件,它们为开发者提供了简单易用的持久化框架,使得在面向对象的编程环境下可以轻松地管理和操作数据库,提升了开发效率...
2. 持久化:通过`@Entity`注解,EJB3.0提供了更直观的持久化模型,支持JPA(Java Persistence API),并与Hibernate等ORM框架集成。 3. 自动事务管理:EJB容器自动处理事务,开发者无需关心底层的事务控制。 4. 无...
EJB(Enterprise JavaBeans)3.0是Java EE(Enterprise Edition)平台中的一项核心技术,旨在为构建可扩展、事务处理、多用户安全的分布式业务应用提供标准服务端组件模型。EJB3.0简化了EJB规范,提高了开发者效率,...