2.2.5.3.3.2. 默认值
和其他许多注解一样,在多对多关联中很多值是自动生成. 当双向多对多关联中没有定义任何物理映射时,Hibernate根据以下规则生成相应的值. 关联表名:主表表名+_下划线+从表表名, 关联到主表的外键名:主表名+_下划线+主表中的主键列名. 关联到从表的外键名:主表中用于关联的属性名+_下划线+从表的主键列名. 以上规则对于双向一对多关联同样有效.
@Entity
public class Store {
@ManyToMany(cascade = CascadeType.PERSIST)
public Set getImplantedIn() {
...
}
}
@Entity
public class City {
... //no bidirectional relationship
}
上面这个例子中,Store_Table作为联接表. 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列联接到City表.
2.2.5.4. 用cascading实现传播性持久化(Transitive persistence)
也许你已经注意到了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章节.
2.2.5.5. 关联关系获取
通过Hibernate你可以获得直接或者延迟获取关联实体的功能. fetch参数可以设置为FetchType.LAZY 或者 FetchType.EAGER. EAGER通过outer join select直接获取关联的对象, 而LAZY(默认值)在第一次访问关联对象的时候才会触发相应的select操作. EJBQL提供了fetch关键字,该关键字可以在进行特殊查询的时候覆盖默认值. 这对于提高性能来说非常有效,应该根据实际的用例来判断是否选择fetch关键字.
2.2.6. 映射复合主键与外键
组合主键使用一个可嵌入的类作为主键表示,因此你需要使用@Id 和@Embeddable两个注解. 还有一种方式是使用@EmbeddedId注解.注意所依赖的类必须实现 serializable以及实现equals()/hashCode()方法. 你也可以如Mapping identifier properties一章中描述的办法使用@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显式使用方式.
2.2.7. 映射二级表(secondary tables)
使用类一级的 @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单元测试代码中还有更多的例子.
2.3. 映射查询
2.3.1. 映射EJBQL/HQL查询
使用注解还可以映射EJBQL/HQL查询. @NamedQuery 和@NamedQueries是可使用在类和包上的注解. 但是它们的定义在session factory/entity manager factory范围中是都可见的. 命名式查询通过它的名字和实际的查询字符串来定义.
javax.persistence.NamedQueries(
@javax.persistence.NamedQuery(name="plane.getAll", query="select p from Plane p")
)
package org.hibernate.test.annotations.query;
...
@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:
表 2.2. Query hints
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
2.3.2. 映射本地化查询
你还可以映射本地化查询(也就是普通SQL查询). 不过这需要你使用@SqlResultSetMapping注解来描述SQL的resultset的结构 (如果你打算定义多个结果集映射,可是使用@SqlResultSetMappings). @SqlResultSetMapping和@NamedQuery, @SqlResultSetMapping一样,可以定义在类和包一级. 但是@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", 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. 这个属性的布尔变量值表明这个查询是否是一个存储过程.
2.4. Hibernate独有的注解扩展
Hibernate 3.1 提供了多种附加的注解,这些注解可以与EJB3的实体混合/匹配使用. 他们被设计成EJB3注解的自然扩展.
为了强化EJB3的能力,Hibernate提供了与其自身特性相吻合的特殊注解. org.hibernate.annotations包已包含了所有的这些注解扩展.
2.4.1. 实体
你可以在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 { ... }
2.4.2. 标识符
@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属性增加一些参数.
2.4.3. 属性
2.4.3.1. 访问类型
访问类型是根据@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;
}
}
2.4.3.2. 公式
有时候,你想让数据库,而非JVM,来替你完成一些计算,也可能想创建某种虚拟列. 你可以使用SQL片段(亦称为公式),而不是将属性映射到(物理)列. 这种属性是只读的(属性值由公求得).
@Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
SQL片段可以是任意复杂的,甚至可包含子查询.
2.4.3.3. 类型
@org.hibernate.annotations.Type 覆盖了Hibernate所用的默认类型:这通常不是必须的,因为类型可以由Hibernate正确推得. 关于Hibernate类型的详细信息,请参考Hibernate使用手册.
@org.hibernate.annotations.TypeDef 和 @org.hibernate.annotations.TypeDefs允许你来声明类型定义. 这些注解被置于类或包一级.注意,对session factory来说, 这些定义将是全局的(即使定义于类一级),并且类型定义必须先于任何使用.
@TypeDefs(
{
@TypeDef(
name="caster",
typeClass = CasterStringType.class,
parameters = {
@Parameter(name="cast", value="lower")
}
)
}
)
package org.hibernate.test.annotations.entity;
...
public class Forest {
@Type(type="caster")
public String getSmallText() {
...
}
当使用组合的用户自定义类型时,你必须自己表示列的定义. @Columns就是为了此目的而引入的.
@Type(type="org.hibernate.test.annotations.entity.MonetaryAmountUserType")
@Columns(columns = {
@Column(name="r_amount"),
@Column(name="r_currency")
})
public MonetaryAmount getAmount() {
return amount;
}
public class MonetaryAmount implements Serializable {
private BigDecimal amount;
private Currency currency;
...
}
2.4.3.4. 索引
通过在列属性(property)上使用@Index注解, 可以在特定列上定义索引,columnNames属性(attribute)将随之被忽略.
@Column(secondaryTable="Cat1")
@Index(name="story1index")
public String getStoryPart1() {
return storyPart1;
}
2.4.3.5. @Parent
在嵌入式对象内部,你可以在那些指向该嵌入式对象所属元素的属性上定义该注解.
@Entity
public class Person {
@Embeddable public Address address;
...
}
@Embeddable
public class Address {
@Parent public Person owner;
...
}
person == person.address.owner
2.4.3.6. 生成的属性
某些属性可以在对数据库做插入或更新操作的时候生成. Hibernate能够处理这样的属性,并触发一个后续的查询来读取这些属性.
@Entity
public class Antenna {
@Id public Integer id;
@Generated(GenerationTime.ALWAYS) @Column(insertable = false, updatable = false)
public String longitude;
@Generated(GenerationTime.INSERT) @Column(insertable = false)
public String latitude;
}
你可以将属性注解为@Generated. 但是你要注意insertability和updatability不要和你选择的生成策略冲突. 如果选择了GenerationTime.INSERT,该属性不能包含insertable列, 如果选择了GenerationTime.ALWAYS,该属性不能包含insertable和updatable列.
@Version属性不可以为 @Generated(INSERT)(设计时), 只能是 NEVER或ALWAYS.
2.4.4. 继承
SINGLE_TABLE 是个功能强大的策略,但有时,特别对遗留系统而言, 是无法加入一个额外的辨别符列. 由此,Hibernate引入了辨别符公式(discriminator formula)的概念: @DiscriminatorFormula是@DiscriminatorColumn的替代品, 它使用SQL片段作为辨别符解决方案的公式( 不需要有一个专门的字段).
@Entity
@DiscriminatorForumla("case when forest_type is null then 0 else forest_type end")
public class Forest { ... }
2.4.5. 关于单个关联关系的注解
默认情况下,当预期的被关联元素不在数据库中(关乎关联列的错误id),致使Hiberante无法解决关联性问题时,Hibernate就会抛出异常. 这对遗留schema和历经拙劣维护的schema而言,这或有许多不便. 此时,你可用 @NotFound 注解让Hibernate略过这样的元素而不是抛出异常. 该注解可用于 @OneToOne (有外键)、 @ManyToOne 、 @OneToMany 或 @ManyToMany 关联.
@Entity
public class Child {
...
@ManyToOne
@NotFound(action=NotFoundAction.IGNORE)
public Parent getParent() { ... }
...
}
有时候删除某实体的时候需要触发数据库的级联删除.
@Entity
public class Child {
...
@ManyToOne
@OnDelete(action=OnDeleteAction.CASCADE)
public Parent getParent() { ... }
...
}
上面这个例子中,Hibernate将生成一个数据库级的级联删除约束.
2.4.5.1. 延迟选项和获取模式
EJB3为延迟加载和获取模式提供了fetch选项,而Hibernate 这方面提供了更丰富的选项集.为了更好的调整延迟加载和获取策略,Hibernate引入了 一些附加的注解:
? @LazyToOne: 定义了 @ManyToOne 和 @OneToOne 关联的延迟选项. LazyToOneOption 可以是 PROXY (例如:基于代理的延迟加载), NO_PROXY (例如:基于字节码增强的延迟加载 - 注意需要在构建期处理字节码) 和 FALSE (非延迟加载的关联)
? @LazyCollection: 定义了 @ManyToMany和 @OneToMany 关联的延迟选项. LazyCollectionOption 可以是TRUE (集合具有延迟性,只有在访问的时候才加载), EXTRA (集合具有延迟性,并且所有的操作都会尽量避免加载集合, 对于一个巨大的集合特别有用,因为这样的集合中的元素没有必要全部加载)和 FALSE (非延迟加载的关联)
? @Fetch: 定义了加载关联关系的获取策略. FetchMode 可以是 SELECT (在需要加载关联的时候触发select操作), SUBSELECT (只对集合有效,使用了子查询策略,详情参考Hibernate参考文档) or JOIN (在加载主实体(owner entity)的时候使用SQL JOIN来加载关联关系). JOIN 将覆写任何延迟属性 (通过JOIN策略加载的关联将不再具有延迟性).
The Hibernate annotations overrides the EJB3 fetching options.
Hibernate注解覆写了EJB3提供的获取(fetch)选项.
表 2.3. 延迟和获取选项的等效注解
Annotations Lazy Fetch
@[One|Many]ToOne](fetch=FetchType.LAZY) @LazyToOne(PROXY) @Fetch(SELECT)
@[One|Many]ToOne](fetch=FetchType.EAGER) @LazyToOne(FALSE) @Fetch(JOIN)
@ManyTo[One|Many](fetch=FetchType.LAZY) @LazyCollection(TRUE) @Fetch(SELECT)
@ManyTo[One|Many](fetch=FetchType.EAGER) @LazyCollection(FALSE) @Fetch(JOIN)
2.4.6. 关于集合类型的注解
2.4.6.1. 参数注解
以下是可能的设置方式
? 用@BatchSizebatch设置集合的batch大小
? 用@Where注解设置Where子句
? 用注解@Check来设置check子句
? 用注解@OrderBy来设置SQL的order by子句
? 利用@OnDelete(action=OnDeleteAction.CASCADE) 注解设置级连删除策略
你也可以利用@Sort注解定义一个排序比较器(sort comparator), 表明希望的比较器类型,无序、自然顺序或自定义排序,三者择一.若你想用你自己实现的comparator, 你还需要利用comparator属性(attribute)指明实现类.
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Sort(type = SortType.COMPARATOR, comparator = TicketComparator.class)
@Where(clause="1=1")
@OnDelete(action=OnDeleteAction.CASCADE)
public SortedSet getTickets() {
return tickets;
}
关于这些注解更详细的信息,请参阅此前的描述.
2.4.6.2. 更多的集合类型
比EJB3更胜一筹的是,Hibernate Annotations支持真正的 List和Array. 映射集合的方式和以前完全一样,只不过要新增@IndexColumn注解. 该注解允许你指明存放索引值的字段.你还可以定义代表数据库中首个元素的索引值(亦称为索引基数). 常见取值为0或1.
@OneToMany(cascade = CascadeType.ALL)
@IndexColumn(name = "drawer_position", base=1)
public List getDrawers() {
return drawers;
}
注意
假如你忘了设置@IndexColumn, Hibernate会采用包(bag)语义(译注:即允许重复元素的无序集合).
Hibernate注解支持true Map映射, 如果没有设置@javax.persistence.MapKey, hibernate将key元素或嵌入式对象直接映射到他们所属的列. 要覆写默认的列,可以使用以下注解: @org.hibernate.annotations.MapKey适用的key为基本类型或者嵌入式对象, @org.hibernate.annotations.MapKey适用的key为实体.
Hibernate Annotations还支持核心类型集合(Integer, String, Enums, ......)、 可内嵌对象的集合,甚至基本类型数组.这就是所谓的元素集合.
元素集合可用@CollectionOfElements来注解(作为@OneToMany的替代). 为了定义集合表(译注:即存放集合元素的表,与下面提到的主表对应),要在关联属性上使用@JoinTable注解, joinColumns定义了介乎实体主表与集合表之间的连接字段(inverseJoincolumn是无效的且其值应为空). 对于核心类型的集合或基本类型数组,你可以在关联属性上用@Column来覆盖存放元素值的字段的定义. 你还可以用@AttributeOverride来覆盖存放可内嵌对象的字段的定义. 要访问集合元素,需要将该注解的name属性值设置为"element"("element"用于核心类型,而"element.serial" 用于嵌入式对象的serial属性).要访问集合的index/key,则将该注解的name属性值设置为"key".
@Entity
public class Boy {
private Integer id;
private Set nickNames = new HashSet();
private int[] favoriteNumbers;
private Set favoriteToys = new HashSet();
private Set characters = new HashSet();
@Id @GeneratedValue
public Integer getId() {
return id;
}
@CollectionOfElements
public Set getNickNames() {
return nickNames;
}
@CollectionOfElements
@JoinTable(
table=@Table(name="BoyFavoriteNumbers"),
joinColumns = @JoinColumn(name="BoyId")
)
@Column(name="favoriteNumber", nullable=false)
@IndexColumn(name="nbr_index")
public int[] getFavoriteNumbers() {
return favoriteNumbers;
}
@CollectionOfElements
@AttributeOverride( name="element.serial", column=@Column(name="serial_nbr") )
public Set getFavoriteToys() {
return favoriteToys;
}
@CollectionOfElements
public Set getCharacters() {
return characters;
}
...
}
public enum Character {
GENTLE,
NORMAL,
AGGRESSIVE,
ATTENTIVE,
VIOLENT,
CRAFTY
}
@Embeddable
public class Toy {
public String name;
public String serial;
public Boy owner;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSerial() {
return serial;
}
public void setSerial(String serial) {
this.serial = serial;
}
@Parent
public Boy getOwner() {
return owner;
}
public void setOwner(Boy owner) {
this.owner = owner;
}
public boolean equals(Object o) {
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
final Toy toy = (Toy) o;
if ( !name.equals( toy.name ) ) return false;
if ( !serial.equals( toy.serial ) ) return false;
return true;
}
public int hashCode() {
int result;
result = name.hashCode();
result = 29 * result + serial.hashCode();
return result;
}
}
在嵌入式对象的集合中,可以使用 @Parent注解嵌入式对象的某属性. 该属性指向该嵌入式对象所属的集合实体.
注意
旧版的Hibernate Annotations用@OneToMany来标识集合元素. 由于语义矛盾,我们引入了@CollectionOfElements注解. 用@OneToMany来标识集合元素的这种旧有方式目前尚有效, 但是不推荐使用,而且在以后的发布版本中不再支持这种方式.
2.4.7. 缓存
为了优化数据库访问,你可以激活所谓的Hibernate二级缓存.该缓存是可以按每个实体和集合进行配置的.
@org.hibernate.annotations.Cache定义了缓存策略及给定的二级缓存的范围. 此注解适用于根实体(非子实体),还有集合.
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet getTickets() {
return tickets;
}
@Cache(
CacheConcurrencyStrategy usage(); (1)
String region() default ""; (2)
String include() default "all"; (3)
)
(1) usage: 给定缓存的并发策略(NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL)
(2) region (可选的):缓存范围(默认为类的全限定类名或是集合的全限定角色名)
(3) include (可选的):值为all时包括了所有的属性(proterty), 为non-lazy时仅含非延迟属性(默认值为all)
2.4.8. 过滤器
Hibernate具有数据过滤器的概念,可在运行期应用于一个给定的session.过滤器需要事先定义好.
@org.hibernate.annotations.FilterDef 或 @FilterDefs 定义过滤器声明,为同名过滤器所用. 过滤器声明带有一个name()和一个parameters()数组,@ParamDef包含name和type, 你还可以为给定的@filterDef定义一个defaultCondition()参数, 当@Filter中没有任何定义时,可使用该参数定义缺省条件. @FilterDef (s)可以在类或包一级进行定义.
现在我们来定义应用于实体或集合加载时的SQL过滤器子句.我们使用@Filter,并将其置于实体或集合元素上.
@Entity
@FilterDef(name="minLength", parameters={ @ParamDef( name="minLength", type="integer" ) } )
@Filters( {
@Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length"),
@Filter(name="minLength", condition=":minLength <= length")
} )
public class Forest { ... }
2.4.9. 查询
由于Hibernate引入了 @org.hibernate.annotations.NamedQuery, @org.hibernate.annotations.NamedQueries, @org.hibernate.annotations.NamedNativeQuery 和 @org.hibernate.annotations.NamedNativeQueries 命名式查询, 因此Hibernate在命名式查询上比EBJ3规范中所定义的命名式查询提供了更多的特性. 他们在标准版中添加了可作为替代品的一些属性(attributes):
? flushMode: 定义查询的刷新模式(Always, Auto, Commit或Never)
? cacheable: 查询该不该被缓存
? cacheRegion: 若查询已被缓存时所用缓存的范围
? fetchSize: 针对该查询的JDBC statement单次获取记录的数目
? timeout: 查询超时
? callable: 仅用于本地化查询(native query),对于存储过程设为true
? comment: 一旦激活注释功能,我们会在向数据库交送查询请求时看到注释
? cacheMode: 缓存交护模式(get, ignore,normal,或refresh)
? readOnly: 不管是否从查询获取元素都是在只读模式下
注意,EJB3已公开的最终草案中引入了@QueryHint的概念, 这可能是定义hints更好的方法.