至此,我们已经展示了很多跟关联有关的声明定义以及属性细节.
下面我们将深入介绍@JoinTable注解,该注解定义了联接表的表名,联接列数组(注解中定义数组的格式为{ A, B, C
}),以及inverse联接列数组. 后者是关联表中关联到Employee主键的列(the "other side").
正如前面所示,被关联端不必也不能描述物理映射:
只需要一个简单的mappedBy参数,该参数包含了主体端的属性名,这样就绑定双方的关系.
和其他许多注解一样,在多对多关联中很多值是自动生成.
当双向多对多关联中没有定义任何物理映射时,Hibernate根据以下规则生成相应的值.
关联表名:主表表名+_下划线+从表表名,关联到主表的外键名:主表名+_下划线+主表中的主键列名.
关联到从表的外键名:主表中用于关联的属性名+_下划线+从表的主键列名. 以上规则对于双向一对多关联同样有效. @Entity
public class Store { @ManyToMany(cascade = CascadeType.PERSIST)
public Set 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 getCustomers() { ... } } @Entity
public class Customer { @ManyToMany(mappedBy="customers") public
Set 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 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范围中是都可见的. 命名式查询通过它的名字和实际的查询字符串来定义. select p from
Plane p ... ... @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;
} }