返回介绍

为模型对象添加标注

发布于 2025-04-21 21:42:13 字数 23321 浏览 0 评论 0 收藏

可以在许多 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 是方法的局部变量,所以不可能会有多个线程同时使用这一变量。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。