EJB3.0-JPA实体的注解规范以及Hibernate特有的扩展(下)

[size=medium] 有时候,你想让数据库,而非JVM,来替你完成一些计算,也可能想创建某种虚拟列.
你可以使用SQL片段(亦称为公式),而不是将属性映射到(物理)列. 这种属性是只读的(属性值由公求得).
@Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()

SQL片段可以是任意复杂的,甚至可包含子查询.

@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;
...
}

通过在列属性(property)上使用@Index注解,可以在特定列上定义索引,columnNames属性(attribute)将随之被忽略.

@Column(secondaryTable="Cat1")
@Index(name="story1index")
public String getStoryPart1() {
return storyPart1;
}

在嵌入式对象内部,你可以在那些指向该嵌入式对象所属元素的属性上定义该注解.
@Entity
public class Person {
@Embeddable public Address address;
...
}

@Embeddable
public class Address {
@Parent public Person owner;
...
}

person == person.address.owner

某些属性可以在对数据库做插入或更新操作的时候生成.
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.

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 { ... }

默认情况下查询顶级实体,Hibernate不会加入带鉴别器列的约束条件子句.
但是如果该列中还包含了和继承层次无关的值(通过@DiscriminatorValue)
就会很不方便.为了解决这个问题,你可以在类上使用@ForceDiscriminator注解
(将该注解放在@DiscriminatorColumn后面).
这样Hibernate在加载实体的时候就可以列出对应的值.

默认情况下,当预期的被关联元素不在数据库中(关乎关联列的错误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将生成一个数据库级的级联删除约束.

Hibernate生成的外键约束的名字可读性相当差,
你可以使用@ForeignKey注解覆盖自动生成的值.

@Entity
public class Child {
...
@ManyToOne
@ForeignKey(name="FK_PARENT")
public Parent getParent() { ... }
...
}

alter table Child add constraint FK_PARENT foreign key (parent_id) references Parent

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)选项.

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)

以下是可能的设置方式
用@BatchSizebatch设置集合的batch大小
用@Where或@WhereJoinTable注解设置Where子句,
这两种注解分别应用于目标实体和关联表

用注解@Check来设置check子句
用注解@OrderBy来设置SQL的order by子句
利用@OnDelete(action=OnDeleteAction.CASCADE) 注解设置级连删除策略

你也可以利用@Sort注解定义一个排序比较器(sort comparator),表明希望的比较器类型,无序、自然顺序或自定义排序,三者择一.若你想用你自己实现的comparator,你还需要利用comparator属性(attribute)指明实现类.
注意你需要使用SortedSet或SortedMap接口

@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<Ticket> getTickets() {
return tickets;
}

关于这些注解更详细的信息,请参阅此前的描述.
Hibernate生成的外键约束的名字可读性相当差,你可以使用@ForeignKey注解覆盖自动生成的值. 注意该注解应该置于关联关系的主体端,inverseName
指向另一端的约束.
@Entity
public class Woman {
...
@ManyToMany(cascade = {CascadeType.ALL})
@ForeignKey(name = "TO_WOMAN_FK", inverseName = "TO_MAN_FK")
public Set<Man> getMens() {
return mens;
}
}

alter table Man_Woman add constraint TO_WOMAN_FK foreign key (woman_id) references Woman
alter table Man_Woman add constraint TO_MAN_FK foreign key (man_id) references Man

比EJB3更胜一筹的是,Hibernate Annotations支持真正的List和Array.
映射集合的方式和以前完全一样,只不过要新增@IndexColumn注解.
该注解允许你指明存放索引值的字段.你还可以定义代表数据库中首个元素的索引值(亦称为索引基数).
常见取值为0或1.
@OneToMany(cascade = CascadeType.ALL)
@IndexColumn(name = "drawer_position", base=1)
public List<Drawer> getDrawers() {
return drawers;
}

假如你忘了设置@IndexColumn,Hibernate会采用bag语义(译注:即允许重复元素的无序集合).
如果你既想使用bag语义,但是又不希望受制于其约束语义,可以考虑使用@CollectionId注解.

Hibernate注解支持true Map映射,如果没有设置@javax.persistence.MapKey,hibernate将key元素或嵌入式对象直接映射到他们所属的列.
要覆写默认的列,可以使用以下注解:
@org.hibernate.annotations.MapKey适用的key为基本类型
(默认为mapkey)或者嵌入式对象,
@org.hibernate.annotations.MapKey适用的key为实体.

双向关联的其中一端在使用@IndexColumn或者 @org.hibernate.annotations.MapKey[ManyToMany]注解需要考虑一些特殊的因素.如果子类存在某个属性映射到索引列,这种情况下是没有问题的,我们可以继续在集合映射的时候使用mappedBy,如下:
@Entity
public class Parent {
@OneToMany(mappedBy="parent")
@org.hibernate.annotations.MapKey(columns=@Column(name="name"))
private Map<String, Child> children;
...
}

@Entity
public class Parent {
...
@Basic
private String name;

@ManyToOne
@JoinColumn(name="parent_id", nullable=false)
private Parent parent;
...
}

但是,如果在子类中没有该属性,我们就不能认为这种关联是真正的双向关联(也就是在关联的一端有信息而另一端没有).因此在这种情况下,我们就不能使用mappedBy将其映射集合.取而代之为下面这种映射方式:
@Entity
public class Parent {
@OneToMany
@org.hibernate.annotations.MapKey(columns=@Column(name="name"))
@JoinColumn(name="parent_id", nullable=false)
private Map<String, Child> children;
...
}

@Entity
public class Parent {
...
@ManyToOne
@JoinColumn(name="parent_id", insertable=false, updatable=false, nullable=false)
private Parent parent;
...
}

注意在上面的映射中,关联的集合端负责更新外键.
另外一个有趣的特征就是可以给bag集合定义一个代理主键.通过这种方式优雅的去除了bag的缺点:update和remove操作更加有效率,每次查询或每个实体可以超过一个EAGER bag.该主键被保存在集合表的一个附加列,该列对于Java应用不可见.@CollectionId注解将一个集合标注为id bag,同时还可以覆写主键列,主键类型以及生成器策略.生成器策略可以是identity,也可以是应用中任何已定义的生成器的名字.

@Entity
@TableGenerator(name="ids_generator", table="IDS")
public class Passport {
...

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="PASSPORT_VISASTAMP")
@CollectionId(
columns = @Column(name="COLLECTION_ID"),
type=@Type(type="long"),
generator = "ids_generator"
)
private Collection<Stamp> visaStamp = new ArrayList();
...
}

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<String> nickNames = new HashSet<String>();
private int[] favoriteNumbers;
private Set<Toy> favoriteToys = new HashSet<Toy>();
private Set<Character> characters = new HashSet<Character>();

@Id @GeneratedValue
public Integer getId() {
return id;
}

@CollectionOfElements
public Set<String> 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<Toy> getFavoriteToys() {
return favoriteToys;
}

@CollectionOfElements
public Set<Character> 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来标识集合元素的这种旧有方式目前尚有效,
但是不推荐使用,而且在以后的发布版本中不再支持这种方式.

为了优化数据库访问,你可以激活所谓的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<Ticket> getTickets() {
return tickets;
}

@Cache(
CacheConcurrencyStrategy usage();
String region() default "";
String include() default "all";
)

usage: 给定缓存的并发策略(NONE,READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL)

region (可选的):缓存范围(默认为类的全限定类名或是集合的全限定角色名)

include (可选的):值为all时包括了所有的属性(proterty),为non-lazy时仅含非延迟属性(默认值为all)

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 { ... }

当这些集合使用关联表来表示关系的时候,你可能需要对于关联表或者目标实体表应用过滤条件.使用@Filter注解可以在目标实体上添加改类约束.
但是如果你打算在关联表上使用,就需要使用@FilterJoinTable注解.

@OneToMany
@JoinTable
//filter on the target entity table
@Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length")
//filter on the association table
@FilterJoinTable(name="security", condition=":userlevel >= requredLevel")
public Set<Forest> getForests() { ... }

由于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: 不管是否从查询获取元素都是在只读模式下

通过@QueryHint注解可以在
@javax.persistence.NamedQuery
中设置hints.另一个重要的优势是可以将这些注解应用到包上[/size]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值