- 译者序
- 前言
- 本书怎么使用
- 本书排版字体约定
- 本书网站
- 致谢
- 第一部分 Hibernate 快速入门
- 第 1 章 安装和设置
- 第 2 章 映射简介
- 第 3 章 驾驭 Hibernate
- 第 4 章 集合与关联
- 第 5 章 更复杂的关联
- 第 6 章 自定义值类型
- 第 7 章 映射标注
- 第 8 章 条件查询
- 第 9 章 浅谈 HQL
- 第二部分 与其他工具的集成
- 第 10 章 将 Hibernate 连接到 MySQL
- 第 11 章 Hibernate 与 Eclipse:Hibernate Tools 使用实战
- 第 12 章 Maven 进阶
- 第 13 章 Spring 入门:Hibernate 与 Spring
- 第 14 章 画龙点睛:用 Stripes 集成 Spring 和 Hibernate
- 附录 A Hibernate 类型
- 附录 B Criteria API
- 附录 C Hibernate SQL 方言
- 附录 D Spring 事务支持
- 附录 E 参考资源
- 作者简介
- 封面介绍
为模型对象添加标注
可以在许多 Java 元素上应用标注。在使用 Hibernate 时,通常关注的是对类和它的字段进行标注,以指定如何将模型对象映射到数据库模式。这类似于 XML 映射文档是被映射的类和属性的结构化描述方式。已经进行了足够的背景介绍和解释,接下来就深入主题,以具体的实例(在第 6 章中开发的完整例子)来看看如何使用标注对类进行映射。
应该怎么做
例 7-5 演示了一种对 Artist 类进行标注的方法。本章只是介绍 Hibernate Annotations 的基础,如果你想更加彻底地了解这些标注,请参考 Hibernate Annotations 项目网站,它的网址是 http://annotations.hibernate.org( [1] )。为了节省篇幅,对下载的完整源代码中的标注类进行了一定的压缩,压缩了空白字符,省略了 JavaDoc 注释。由于这些源代码是手工编写的类,而不是像前几章中用代码生成工具生成的类,所以就有空间为这些代码加入更多、更详细的 JavaDoc 注释。建议你也看看下载到的源代码,很有价值。
例 7-5:标注 Artist 类
package com.oreilly.hh.data;
import java.util.*;
import javax.persistence.*;❶
import org.hibernate.annotations.Index;
@Entity❷
@Table(name="ARTIST")
@NamedQueries({
@NamedQuery(name="com.oreilly.hh.artistByName",
query="from Artist as artist where upper(artist.name)=upper(:name)")
})
public class Artist{
@Id❸
@Column(name="ARTIST_ID")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
@Column(name="NAME",nullable=false, unique=true)❹
@Index(name="ARTIST_NAME",columnNames={"NAME"})❺
private String name;
@ManyToMany❻
@JoinTable(name="TRACK_ARTISTS",
joinColumns={@JoinColumn(name="TRACK_ID")},
inverseJoinColumns={@JoinColumn(name="ARTIST_ID")})
private Set<Track>tracks;
@ManyToOne❼
@JoinColumn(name="actualArtist")
private Artist actualArtist;
public Artist(){}
public Artist(String name, Set<Track>tracks, Artist actualArtist){
this.name=name;
this.tracks=tracks;
this.actualArtist=actualArtist;
}
public Integer getId(){return id;}
public void setId(Integer id){
this.id=id;
}
public String getName(){return name;}
public void setName(String name){
this.name=name;
}
public Artist getActualArtist(){return actualArtist;}
public void setActualArtist(Artist actualArtist){
this.actualArtist=actualArtist;
}
public Set<Track>getTracks(){return tracks;}
public void setTracks(Set<Track>tracks){
this.tracks=tracks;
}
/**
*Produce a human-readable representation of the artist.
*
*@return a textual description of the artist.
*/
public String toString(){
StringBuilder builder=new StringBuilder();
builder.append(getClass().getName()).append("@");
builder.append(Integer.toHexString(hashCode())).append("[");
builder.append("name").append("='").append(getName()).append("'");
builder.append("actualArtist").append("='").append(getActualArtist());
builder.append("'").append("]");
return builder.toString();
}
}
❶为了使用非核心 Java 语言自身的标注,例如我们在这里讨论的与持久化相关的标注,得先导入它们。标注其实也只是 Java 类(尽管这些类会实现一个特殊的接口,但它们的声明方法有些意思,相关介绍已经超出本章讨论的范围,有兴趣的话可以阅读第 14 章),所以使用普通的 import 语句像这样导入它们就可以了。
如前所述,我们在这里需要使用的大多数标注都是由标准的 EJB 3 标注(在 javax.persistence 包中定义)演化而来的。我们之所以需要一个 Hibernate 特定的标注,是为了能够获得一个特定的索引 Maven 会自动下载并让我们能够使用 Java Persistence API,因为我们在更新过的 build.xml 中要求它是 Hibernate Annotations 的一个依赖。我们可以像这样单独地使用 Java Persistence API,因为 JDK 中专门设计让它在 Java SE 环境中可以单独使用,在 Java EE 中也内建了一部分对 EJB 的支持。如果你想了解更多内容,它的主页( [2] )就是一个好的起点。
❷应用到 Artist 类上的这组标注是一个整体。Entity 标注将这个类标记为可持久化的。Table 标注是可选的,标注处理器将为标注提供非常合理的默认假设,但是我们在这里想演示如何明确地指定表名,以防止把错误的名称连接到现有的数据库。
注意:这种查询语言关系表明在很大程度上 Hibernate 确实影响了 EJB 3 的发展方向。
那么我们的查询在 Java 源代码中到底做了什么?唉,这是在使用标注时,Hibernate 原生接口表现出来的另一个缺点:没有放置命名查询的地方。如果我们使用 JPA EntityManager,就可以将命名查询放在一个 persistence.xml 文件中,这样就保留了将 SQL 语句和 Java 源代码互相分离的优点。由于在本书中我们坚持使用会话接口,这样当我们使用标注而不是 XML 映射文件时,就丧失了使用命名查询的优点。但是我们仍然可以用 HQL 编写查询,使用它的所有功能。如果换用 JPA 接口,就得使用 JPAQL(它是 HQL 的一个子集)。
❸这里演示了如何标注映射属性。这是一个特殊的例子,因为要标注的属性也是对象的惟一标识符,如 @Id 标注所示。可以用标注指定不同的 ID 生成策略,就像用 Hibernate 的 XML 映射文档一样(标注旨在完全取代现有 O/R 层的功能,标准的 JPA 选择以及 Hibernate 自己的标注,实际上你差不多可以做想要的任何事情了)。为生成风格选择 AUTO,相当于在 XML 中指定<generator class="native"/>的基于标注的等价物。它告诉 Hibernate 使用对于数据库来说最自然的任何处理方法。
与实体级的标注一样,你可以省略一些选择(例如列名),默认的选项就相当合理,但我们在这里想演示当你有特殊需要时如何进行配置。事实上,根本不需要为属性添加标注—JPA 将假设实体的所有属性都要映射,除非特别指定(要是通过标注的话,自然是:@Transient 用于这一目的)。
也要注意,我们是将标注加到了实际的字段,而不是访问器方法上。这是告诉 Hibernate 直接访问字段,而访问器在一个类中适合在运行时为其他类提供抽象,但这样与持久化又不能保持兼容。在许多情况下,你可能想让 Hibernate 使用访问器方法,只要将标注放到相应的 getter 或 setter 方法上就可以了。你需要挑出一个方法或其他方法,但属性之间的混合和匹配还不支持(通过 JPA,再加上 Hibernate 的一些扩展……即便你能够这样做,也可能引起其他的混乱)。
❹当对列进行映射时,可以用许多可选的属性来控制映射结果,例如是否为 null、惟一约束等等,就像在 XML 映射文件中的配置一样。
❺为了能够指定一个列应该有一个索引(以及如何建立索引),是我们在这个例子中增加 Hibernate 标注的一个原因。@Index 标签不是标准 JPA 标注的一部分,它是一个有用的 Hibernate 扩展。使用这个标注就让我们的代码需要依靠 Hibernate,但除了这是一本有关 Hibernate 的书这一事实以外,如前所述(后面也会介绍),还有许多原因可以解释为什么你将经常要做出同样的选择。
❻和映射文档一样,在关联配置上也有更多的选项。在这个例子中,我们描述了一个 Track 之间的多对多关系,显式地描述出在数据库中如何表达这一关系。
❼有时你不需要在标注中配置很多东西,即使你很清楚这些配置。这个例子是为了演示标注为何要比 XML 映射文件更加简洁。我们使用 @JoinColumn 标注来设置列名(与基于 XML 的配置方法一样)。如果没有这个标注,也能正常运行,但是默认的列名稍微有点冗长:ACTUALARTIST_ARTIST_ID。
除了以上这段代码,这个类没什么可介绍的了,它是一个简单的数据 bean,与前几章中根据映射文档生成的数据类非常相似。下载到的代码中的 JavaDoc 注释,详细的解释了字段和方法的作用,比前面用工具生成的代码中的注释详细多了。
这个标注过的类生成的 ARTIST 数据表,与前面几章中根据 Artist.hbm.xml 映射文档而得到的 ARTIST 表完全相同。
标注 Track 类
标注过的 Artist 类需要引用 Track 类。例 7-6 演示了 Track 类中的标注,其中也引入了一些新的问题。
例 7-6:标注 Track 类
package com.oreilly.hh.data;
import java.sql.Time;
import java.util.*;
import javax.persistence.*;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.Index;
@Entity
@Table(name="TRACK")
@NamedQueries({
@NamedQuery(name="com.oreilly.hh.tracksNoLongerThan",
query="from Track as track where track.playTime<=:length")
})
public class Track{
@Id
@Column(name="TRACK_ID")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
@Column(name="TITLE",nullable=false)
@Index(name="TRACK_TITLE",columnNames={"TITLE"})
private String title;
@Column(nullable=false)
private String filePath;
@Temporal(TemporalType.TIME)❶
private Date playTime;
@ManyToMany
@JoinTable(name="TRACK_ARTISTS",
joinColumns={@JoinColumn(name="ARTIST_ID")},
inverseJoinColumns={@JoinColumn(name="TRACK_ID")})
private Set<Artist>artists;
@Temporal(TemporalType.DATE)❷
private Date added;
@CollectionOfElements❸
@JoinTable(name="TRACK_COMMENTS",
joinColumns=@JoinColumn(name="TRACK_ID"))
@Column(name="COMMENT")
private Set<String>comments;
@Enumerated(EnumType.STRING)❹
private SourceMedia sourceMedia;
@Embedded❺
@AttributeOverrides({❻
@AttributeOverride(name="left",column=@Column(name="VOL_LEFT")),
@AttributeOverride(name="right",column=@Column(name="VOL_RIGHT"))
})
StereoVolume volume;
public Track(){}
public Track(String title, String filePath){
this.title=title;
this.filePath=filePath;
}
public Track(String title, String filePath, Time playTime,
Set<Artist>artists, Date added, StereoVolume volume,
SourceMedia sourceMedia, Set<String>comments){
this.title=title;
this.filePath=filePath;
this.playTime=playTime;
this.artists=artists;
this.added=added;
this.volume=volume;
this.sourceMedia=sourceMedia;
this.comments=comments;
}
public Date getAdded(){return added;}
public void setAdded(Date added){
this.added=added;
}
public String getFilePath(){return filePath;}
public void setFilePath(String filePath){
this.filePath=filePath;
}
public Integer getId(){return id;}
public void setId(Integer id){
this.id=id;
}
public Date getPlayTime(){return playTime;}
public void setPlayTime(Date playTime){
this.playTime=playTime;
}
public String getTitle(){return title;}
public void setTitle(String title){
this.title=title;
}
public Set<Artist>getArtists(){return artists;}
public void setArtists(Set<Artist>artists){
this.artists=artists;
}
public Set<String>getComments(){return comments;}
public void setComments(Set<String>comments){
this.comments=comments;
}
public SourceMedia getSourceMedia(){return sourceMedia;}
public void setSourceMedia(SourceMedia sourceMedia){
this.sourceMedia=sourceMedia;
}
public StereoVolume getVolume(){return volume;}
public void setVolume(StereoVolume volume){
this.volume=volume;
}
public String toString(){❼
StringBuilder builder=new StringBuilder();
builder.append(getClass().getName()).append("@");
builder.append(Integer.toHexString(hashCode())).append("[");
builder.append("title").append("='").append(getTitle()).append("'");
builder.append("volume").append("='").append(getVolume()).append("'");
builder.append("sourceMedia").append("='").append(getSourceMedia());
builder.append("'").append("]");
return builder.toString();
}
}
❶与日期类型 Date(或者是时间戳 timestamp,包括时间和日期)相比,由于 Java 缺少明确的类来表示一天中的时间,我们就需要一种方法来声明如何使用 Date 类。@Temporal 标注提供了这样的功能。在这个例子中我们标注 playTime 对应于 SQL 中的 TIME 列。如果忽略这个标注,默认的列类型将是 TIMESTAMP。
❷added 属性,虽然它与 playTime 具有同样的 Date 类型,但对应于一个 DATE 列。
❸@CollectionOfElements 标注也是一个 Hibernate 扩展,对于到简单值类型集合的关联,它为我们控制这些类型映射到的表和列提供了一种更简单的方法。这是我们之所以要使用非标准标注的主要原因之一。使用纯 JPA,就根本不能直接映射简单值类型(例如 String 或 Integer)的集合。这需要声明一个完整的实体类来持有这样的值,接着再映射这个类。对于那些习惯用 Hibernate 映射 POJO(Plain Old Java Object)的灵活性的人,这可能是一大退步。
❹另一方面,对于内建枚举类型的映射,JPA 提供了强大的支持,这是 Java 5 enum 问世以来给我们带来的一个好处(类型安全的枚举类型模式的广泛采纳成就了这一语言功能)。这个标注比我们在第 6 章中用过的要更加简单!
❺这是用 JPA 标注来映射复合自定义类型的标准用法,相当于例 6-10。JPA 规范要求当我们进行这样的映射时,同时要将 StereoVolume 类标记为可嵌入的:
package com.oreilly.hh.data;
import java.io.Serializable;
import javax.persistence.Embeddable;
/**
*A simple structure encapsulating a stereo volume level.
*/
@Embeddable
public class Stereovolume implements Serializable{
……
}
(事实上,Hibernate 可以很容易地映射嵌套类,而不由我们做这些额外的工作。不过以后的发行版本可能会更加严格地坚持遵守规范,所以按规矩办事并无大碍。)
❻再一次,我们添加了超过实际需要的标注,为的是生成与基于 XML 的映射版本的例子中完全一样的数据库模式。如果不用 @AttributeOverrides 标注,用于保存两个音量的列将是 LEFT 和 RIGHT(StereoVolume 类中的属性名称),而不是 VOL_LEFT 和 VOL_RIGHT。
❼为了让我们的测试程序可以打印输出与原来的旧方法一样的信息,我们复制了 Hibernate 代码生成器为我们创建的 toString()实现。注意,我们可以借这个机会来更新它们,使用 StringBuilder,而没有必要非得使用线程安全( [3] )(但速度更慢)的 StringBuffer。
这套标注可以创建 TRACK、TRACK_ARTISTS 以及 TRACK_COMMENTS 数据表,与例 2-1 到例 6-10 中使用 Track.hbm.xml 生成的数据库模式一样。
标注 Album 类
Album 类是目前我们构建的其他示例的核心模型类。例 7-7 演示了如何对它进行标注,以重新创建我们正在处理的数据库模式和映射,同时也引入几个不用再多介绍的概念。
例 7-7:标注 Album 类
package com.oreilly.hh.data;
import java.util.*;
import javax.persistence.*;
import org.hibernate.annotations.CollectionOfElements;❶
import org.hibernate.annotations.Index;
import org.hibernate.annotations.IndexColumn;
@Entity
@Table(name="ALBUM")
public class Album{
@Id
@Column(name="ALBUM_ID")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
@Column(name="TITLE",nullable=false)
@Index(name="ALBUM_TITLE",columnNames={"TITLE"})
private String title;
@Column(nullable=false)
private Integer numDiscs;
@ManyToMany(cascade=CascadeType.ALL)
@JoinTable(name="ALBUM_ARTISTS",
joinColumns=@JoinColumn(name="ARTIST_ID"),
inverseJoinColumns=@JoinColumn(name="ALBUM_ID"))
private Set<Artist>artists;
@CollectionOfElements
@JoinTable(name="ALBUM_COMMENTS",
joinColumns=@JoinColumn(name="ALBUM_ID"))
@Column(name="COMMENT")
private Set<String>comments;
@Temporal(TemporalType.DATE)
private Date added;
@CollectionOfElements❷
@IndexColumn(name="LIST_POS")❸
@JoinTable(name="ALBUM_TRACKS",
joinColumns=@JoinColumn(name="ALBUM_ID"))
private List<AlbumTrack>tracks;
public Album(){}
public Album(String title, int numDiscs, Set<Artist>artists,
Set<String>comments, List<AlbumTrack>tracks,
Date added){
this.title=title;
this.numDiscs=numDiscs;
this.artists=artists;
this.comments=comments;
this.tracks=tracks;
this.added=added;
}
public Date getAdded(){return added;}
public void setAdded(Date added){
this.added=added;
}
public Integer getId(){return id;}
public void setId(Integer id){
this.id=id;
}
public Integer getNumDiscs(){return numDiscs;}
public void setNumDiscs(Integer numDiscs){
this.numDiscs=numDiscs;
}
public String getTitle(){return title;}
public void setTitle(String title){
this.title=title;
}
public List<AlbumTrack>getTracks(){return tracks;}
public void setTracks(List<AlbumTrack>tracks){
this.tracks=tracks;
}
public Set<Artist>getArtists(){return artists;}
public void setArtists(Set<Artist>artists){
this.artists=artists;
}
public Set<String>getComments(){return comments;}
public void setComments(Set<String>comments){
this.comments=comments;
}
public String toString(){
StringBuilder builder=new StringBuilder();
builder.append(getClass().getName()).append("@");
builder.append(Integer.toHexString(hashCode())).append("[");
builder.append("title").append("='").append(getTitle()).append("'");
builder.append("tracks").append("='").append(getTracks()).append("'");
builder.append("]");
return builder.toString();
}
}
❶是的,没有 Hibernate 特定的扩展,还是不行。在这个类中至少需要 3 个引用的类。
❷AlbumTrack 不是一个实体(它没有 ID 属性,脱离了 Album 记录,就不能独立地查询它们的实例)。所以我们使用 @CollectionOfElements 标注(就像我们前面映射基本类型一样),而不是 @OneToMany(用于映射实体)。
❸对于集合映射,JPA 和 EJB 只支持 set 之类的语义。如第 5 章所述,能够以特定的顺序来保存记录也很重要,这就是为什么我们要使用像 List 和 array(数组)之类的数据结构,Hibernate 可以容易地映射这种有序集合。不过,如果只用 JPA,就无法映射这样的集合了!Hibernate 的 @IndexColumn 扩展提供了一种权宜之计。
把这个标注和 @JoinColumn 信息组合在一起,就可以让 Hibernate 生成与基于例 5-4 中的 Album.hbm.xml 而得到数据库模式完全一样的结果,其中 ALBUM_TRACKS 表具有一个复合主键(由 ALBUM_ID 和 LIST_POS 组成)。
Album 类与 AlbumTrack 类密切相关,AlbumTrack 的标注过程如例 7-8 所示,同样也会重新创建我们的示例数据库模式。
例 7-8:标注 AlbumTrack 类
package com.oreilly.hh.data;
import java.io.Serializable;
import javax.persistence.*;
@Embeddable❶
public class AlbumTrack{
@ManyToOne(cascade=CascadeType.ALL)❷
@JoinColumn(name="TRACK_ID",nullable=false)
private Track track;
private Integer disc;❸
private Integer positionOnDisc;
public AlbumTrack(){}
public AlbumTrack(Track track, Integer disc, Integer positionOnDisc){
this.track=track;
this.disc=disc;
this.positionOnDisc=positionOnDisc;
}
public Track getTrack(){return track;}
public void setTrack(Track track){
this.track=track;
}
public Integer getDisc(){return disc;}
public void setDisc(Integer disc){
this.disc=disc;
}
public Integer getPositionOnDisc(){return positionOnDisc;}
public void setPositionOnDisc(Integer positionOnDisc){
this.positionOnDisc=positionOnDisc;
}
public String toString(){
StringBuilder builder=new StringBuilder();
builder.append(getClass().getName()).append("@");
builder.append(Integer.toHexString(hashCode())).append("[");
builder.append("track").append("='").append(getTrack()).append("'");
builder.append("]");
return builder.toString();
}
}
❶正如前面对 Album 类映射的讨论,这个类是一个不可以独立存在的实体,所以我们用 @Embeddable 进行标注,和 StereoVolume 的映射一样。
❷即使不是实体,我们也需要告诉 Hibernate 如何处理 track 属性,这个属性会引用一个实体。如果没有这个标注,试图构建数据库模式就会失败。这个标注也让我们能够保留基于 XML 的方法中使用同样的列名称。美中不足的是,到 Track 类的级联请求还不足以在创建新专辑时自动地保存曲目,所以稍后我们将需要回到 AlbumTest 类的早期版本。在本章的 7.3 节中,我们将探究一种能够重新获得这种自动级联的模式生成方法。
❸最后这两个属性表明,在使用标注时,有时你根本不需要提供任何标注。虽然这两个属性声明附近什么也没有,但确实能够被映射,标注处理器提供的默认处理可以实现我们想要的映射。
这个类的其他部分相当直接,比其他几个例子看起来更简短,因为它没有需要管理的 ID 属性,只有其他一些简单属性。
如前所述,这段代码中的映射要求我们在创建新的专辑时,重新负责保存曲目对象。例 5-13 中 Album.hbm.xml 最终的映射配置不需要我们负责保存曲目,所以我们注释掉了 AlbumTest.java 的 addAlbumTrack()中调用 session.save(track)的那行代码。现在我们需要取消对这行的注释,这样才会与例 5-8 保持一致。
可以有效吗
编辑好所有代码后,接下来就可以创建数据库模式了。例 7-9 演示了我们现在用这种基于标注的方法,在运行 ant schema 命令后,得到的大部分结果。其中,为了方便阅读,对创建表格的语句行进行了重新格式化。
例 7-9:使用标注过的类来创建数据库模式(重点部分)
%ant schema
Buildfile:build.xml
Downloading:org/hibernate/hibernate-annotations/3.3.0.ga/hibernate-annotations-
3.3.0.ga.pom❶
Transferring 1K
Downloading:org/hibernate/hibernate/3.2.1.ga/hibernate-3.2.1.ga.pom
Transferring 3K
Downloading:javax/persistence/persistence-api/1.0/persistence-api-1.0.pom
Transferring 1K
Downloading:org/hibernate/hibernate-commons-annotations/3.3.0.ga/hibernate-comm
ons-annotations-3.3.0.ga.pom
Transferring 1K
Downloading:org/hibernate/hibernate-annotations/3.3.0.ga/hibernate-annotations-
3.3.0.ga.jar
Transferring 258K
Downloading:javax/persistence/persistence-api/1.0/persistence-api-1.0.jar
Transferring 50K
Downloading:org/hibernate/hibernate-commons-annotations/3.3.0.ga/hibernate-comm
ons-annotations-3.3.0.ga.jar
Transferring 64K
prepare:
[copy]Copying 1 file to/Users/jim/svn/oreilly/hibernate/current/
examples/ch07/classes
compile:
[javac]Compiling 10 source files to/Users/jim/svn/oreilly/hibernate/
current/examples/ch07/classes
schema:
[hibernatetool]Executing Hibernate Tool with a Hibernate Annotation/EJB3 Config
uration❷
[hibernatetool]1.task:hbm2ddl(Generates database schema)
[hibernatetool]alter table ALBUM_ARTISTS drop constraint FK7BA403FCB99A6003;
……
[hibernatetool]alter table TRACK_COMMENTS drop constraint FK105B2688E424525B;
[hibernatetool]drop table ALBUM if exists;
……
[hibernatetool]drop table TRACK_COMMENTS if exists;
[hibernatetool]create table ALBUM(ALBUM_ID integer generated by default
as identity(start with 1),added date, numDiscs integer,
TITLE varchar(255)not null,
primary key(ALBUM_ID));❸
[hibernatetool]create table ALBUM_ARTISTS(ARTIST_ID integer not null,
ALBUM_ID integer not null,
primary key(ARTIST_ID, ALBUM_ID));
[hibernatetool]create table ALBUM_COMMENTS(ALBUM_ID integer not null,
COMMENT varchar(255));
[hibernatetool]create table ALBUM_TRACKS(ALBUM_ID integer not null,
disc integer, positionOnDisc integer, TRACK_ID integer,
LIST_POS integer not null,
primary key(ALBUM_ID, LIST_POS));
[hibernatetool]create table ARTIST(ARTIST_ID integer generated by default
as identity(start with 1),NAME varchar(255)not null,
actualArtist integer,
primary key(ARTIST_ID),unique(NAME));
[hibernatetool]create table TRACK(TRACK_ID integer generated by default
as identity(start with 1),added date,
filePath varchar(255)not null, playTime time,
sourceMedia varchar(255),TITLE varchar(255)not null,
VOL_LEFT smallint, VOL_RIGHT smallint,
primary key(TRACK_ID));
[hibernatetool]create table TRACK_ARTISTS(ARTIST_ID integer not null,
TRACK_ID integer not null,
primary key(TRACK_ID, ARTIST_ID));
[hibernatetool]create table TRACK_COMMENTS(TRACK_ID integer not null,
COMMENT varchar(255));
[hibernatetool]create index ALBUM_TITLE on ALBUM(TITLE);
[hibernatetool]alter table ALBUM_ARTISTS add constraint FK7BA403FCB99A6003 fore
ign key(ARTIST_ID)references ALBUM;
……
[hibernatetool]alter table TRACK_COMMENTS add constraint FK105B2688E424525B for
eign key(TRACK_ID)references TRACK;
[hibernatetool]9 errors occurred while performing<hbm2ddl>.❹
[hibernatetool]Error#1:java.sql.SQLException:Table not found:ALBUM_ARTISTS
in statement[alter table ALBUM_ARTISTS]
……
BUILD SUCCESSFUL
Total time:5 seconds
❶因为这是我们第一次要求 Maven Ant Tools 提供 Hibernate Annotations,它们将作为依赖而自动下载。
❷这里你可以看到,正在使用标注来驱动数据库模式的创建。
❸如果同例 5-7 中的相应行进行比较,你会发现虽然显示的顺序不同,但列定义是完全一样的。ALBUM_ARTISTS、ALBUM_COMMENTS 以及最具挑战性的 ALBUM_TRACKS 的定义都是同样的情况。我们已经成功地用标注再次创建了原来的数据库模式。
❹在创建数据库模式,当运行时根本不存在数据库时,就会看到这些普通的错误信息。这些错误不会中止创建过程,虽然有错误,但可以认为创建过程还是成功的。
同时,你可以在本章目录下运行 ant db 命令,就像第 5 章做的那样,并比较一下它们各自生成的数据库模式。当然,看看实际的数据,效果应该更好。为了让 CreateTest.java 可以处理基于标注的映射,需要修改它的两个地方,如例 7-10 所示。我们需要导入 Annotation-Configuration 类,并在以前使用 Configuration 类的位置使用它。
例 7-10:对测试类进行调整,以处理基于标注的映射
package com.oreilly.hh;
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
……
public static void main(String args[])throws Exception{
//Create a configuration based on the annotations in our
//model classes.
Configuration config=new AnnotationConfiguration();
config.configure();
……
修改完后,运行 ant ctest 命令就可以创建示例数据,并使用多个 ant db 的实例,依次对数据进行比较。
对 QueryTest.java、QueryTest2.java 以及 AlbumTest.java 进行同样的修改。因为运行 ant qtest 和 ant qtest2 命令的输出与前一章相同,这里就不再演示了。但是我们想演示来自 AlbumTest 的输出内容,因为它需要依赖整个数据库模式,所以可以作为一个不错的完整性检查。用它也可以验证例 7-8 后面介绍的那行用于保存曲目的代码确实被取消注释了。运行 ant atest,对我们这个基于标注的数据库模式进行测试的结果如例 7-11 所示。
例 7-11:运行 AlbumTest,测试标注的使用
atest:
[java]com.oreilly.hh.data.Album@27d19d[title='Counterfeit e.p.'tracks='[
com.oreilly.hh.data.AlbumTrack@bf4c80[track='com.oreilly.hh.data.Track@2e3919[
title='Compulsion'volume='Volume[left=100,right=100]'sourceMedia='CD']'],c
om.oreilly.hh.data.AlbumTrack@3778cf[track='com.oreilly.hh.data.Track@f4d063[t
itle='In a Manner of Speaking'volume='Volume[left=100,right=100]'sourceMedia=
'CD']'],com.oreilly.hh.data.AlbumTrack@dc696e[track='com.oreilly.hh.data.Tra
ck@a5dac0[title='Smile in the Crowd'volume='Volume[left=100,right=100]'sourc
eMedia='CD']'],com.oreilly.hh.data.AlbumTrack@8dbef1[track='com.oreilly.hh.d
ata.Track@c4b579[title='Gone'volume='Volume[left=100,right=100]'sourceMedia=
'CD']'],com.oreilly.hh.data.AlbumTrack@f2f761[track='com.oreilly.hh.data.Tra
ck@8cd64[title='Never Turn Your Back on Mother Earth'volume='Volume[left=100,
right=100]'sourceMedia='CD']'],com.oreilly.hh.data.AlbumTrack@4f1541[track=
'com.oreilly.hh.data.Track@c042ba[title='Motherless Child'volume='Volume[left=
100,right=100]'sourceMedia='CD']']]']
除了 Java 随机将类加载到的内存地址不同以外,例 7-11 与例 5-10 中看到的输出完全相同,和我们希望的一样。
注意:这种方法能行!真的能行!
[1] 如果你跳过本书前面的内容直接阅读这一章,那么最好回去再了解一下用到的这些类和它们之间的关系,至少要浏览一下从第 3 章开始的内容,再继续学习。这样你的收获可能更多。 [2] http://java.sun.com/javaee/technologies/persistence.jsp. [3] 变量 builder 是方法的局部变量,所以不可能会有多个线程同时使用这一变量。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论