- 译者序
- 前言
- 本书怎么使用
- 本书排版字体约定
- 本书网站
- 致谢
- 第一部分 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 参考资源
- 作者简介
- 封面介绍
使用持久化的枚举对象
你可能已经注意到,本章一开始并没有为 SourceMedia 类定义持久化映射。这是因为枚举类型是一个值,只能将它作为一个或多个实体的一部分而进行持久化保存,而不是自成一个实体。
因此,我们还没有做任何映射也就不奇怪了。当我们要实际使用持久化的枚举类型时,才需要这么做,这就是本节要介绍的内容。
应该怎么做
回想一下,我们想在自动唱片机系统中保存音乐曲目的来源媒介。也就是说,我们想在 Track 映射配置中使用 SourceMedia 这一枚举类型。可以简单地在 Track.hbm.xml 中的 class 定义内添加一个新的 property 标签,如例 6-3 所示。
例 6-3:在 Track 映射文档中新增一个 SourceMedia 属性
……
<property name="volume"type="short">
<meta attribute="field-description">How loud to play the track</meta>
</property>
<property name="sourceMedia"type="com.oreilly.hh.SourceMediaType">
<meta attribute="field-description">Media on which track was obtained</meta>
<meta attribute="use-in-tostring">true</meta>
</property>
<set name="comments"table="TRACK_COMMENTS">
……
注意,我们告诉 Hibernate,这个属性是 UserType 接口的实现类,而不是它负责持久化的原始枚举类型。因为 sourceMedia 属性的 type 配置了一个实现了 UserType 接口的实现类,Hibernate 就会委托这个类来为 sourceMedia 属性执行持久化,以及查找与映射相关的 Java 类和 SQL 类型。
现在,运行 ant codegen 命令来更新 Track 类,以引入这个新的属性。
不要这么快
在开发本章示例期间,我遇到过一个奇怪的问题,代码突然不能通过编译,报告没有发现构造函数。起初,这个问题看起来好像和采用 Maven Ant Tasks 进行依赖管理有关,这个问题在我测试时第一次出现。仔细检查源代码,确定是不是哪出问题了。这花费了不少时间,因为这段代码很微妙。可能是因为 Track 类的 sourceMedia 属性被赋予了 SourceMediaType 类型的值(映射管理器),而不是它应该接受的 SourceMedia 类型。
在所有办法都失败以后,我将这个麻烦的 bug 报告给了 Hibernate Tools 团队,他们很快回复说不能重现那个问题,这时我明白是怎么回事了。构建过程之所以中断是因为:Hibernate Tools 需要能够找到编译好的 SourceMediaType 类,映射文档才有意义,也才能够知道这个类是用户自定义的类型。在写这段文字时,我已经编写和编译好了 SourceMediaType 类,这样,当我按例 6-3 所示来更新映射配置并调用 codegen 构建目标时,该类编译好的类文件应该到位了。但当我回头再用 Maven Ant Tasks 进行测试时,其实这时还没有编译好的类存在,就像你在下载代码示例文件以后,再按本书章节介绍的办法来更新创建和查询测试。不过,如果在这种情况下,在 compile 之前运行 codegen 就会让你处于一种类文件不一致、不能编译的境地。但是也不能在 codegen 之前运行 compile,因为测试类的运行要依赖于生成的数据类。
注意:听起来确实有些像经典的 catch-22 问题(因为不合逻辑的规定而造成左右为难的困境)。
很不幸,当没有很谨慎地维护构建指令时,这种令人头痛的循环依赖问题并非不常见。我为 codegen 引入了一个新的依赖,但没有在 build.xml 中定义。因为我们查找错误的方向有误,所以浪费了很多时间,但也正因为这样,我才有机会描述这一问题和它的解决方案。所以,希望当你再遇到类似的情况时,会变得更聪明些。
在清楚地理解了问题出在哪以后,就不难解决了。例 6-4 展示了需要对 build.xml 做的修改。
例 6-4:在构建过程中定义 UserType 类的依赖
<!--Compile the UserType definitions so they can be used in the code
generation phase.-->
<target name="usertypes"depends="prepare"❶
description="Compile custom type definitions needed in by codegen">
<javac srcdir="${source.root}"
includes="com/oreilly/hh/*Type.java"
destdir="${class.root}"
debug="on"
optimize="off"
deprecation="on">
<classpath refid="project.class.path"/>
</javac>
</target>
<!--Generate the java code for all mapping files in our source tree-->
<target name="codegen"depends="usertypes"❷
description="Generate Java source from the O/R mapping files">
❶我们在现有的 codegen 目标之前,创建了一个名为 usertypes 的构建目标,用于编译刚才的用户类型定义。因为自定义类型不会引用任何生成的类,所以可以在 codegen 目标运行之前先编译它们。选择这些自定义类型的最简单的方法,就是利用它们位于 com.oreilly.hh 包这一事实,以及我们在这里使用的命名惯例(Hibernate 本身也将这个包作为它保存类型映射类的地方),在这个包中它们的类文件都以"Type.java"结束(例如,SourceMediaType.java,以及本章稍后介绍的 StereoVolumeType.java)。
如果你不喜欢这样的惯例,则可以把所有文件都列在这里,或是将它们放在其他单独的包中。这种命名方法碰巧很适合我们的需要。
❷接着,我们更新 codegen 构建目标,让它依赖 usertypes。这样可以保证代码生成任务运行以前,它需要的自定义类型映射总能成功编译和使用(这里不需要依赖 prepare,因为现在只需要依赖 usertypes)。
配置好这些额外的设置以后,现在运行 ant codegen 就可以正确地更新 Track 类,以包括新的属性。完整的 Track 构造函数现在应该如下所示:
public Track(String title, String filePath, Date playTime,
Set<Artist>artists, Date added, short volume,
SourceMedia sourceMedia, Set<String>comments){……}
还需要相应地修改 CreateTest.java:
Track track=new Track("Russian Trance",
"vol2/album610/track02.mp3",
Time.valueOf("00:03:30"),
new HashSet<Artist>(),
new Date(),(short)0,SourceMedia.CD,
new HashSet<String>());
track=new Track("Video Killed the Radio Star",
"vol2/album611/track12.mp3",
Time.valueOf("00:03:49"),new HashSet<Artist>(),
new Date(),(short)0,SourceMedia.VHS,
new HashSet<String>());
……
为了得到如图 6-1 所示的结果,我们将"The World' 99"标记为来自于数字音频流,而将其他曲目都标记为来自 CD,而为"Test Tone 1"标记为空的(null)sourceMedia 值。这时,再运行 ant schema 以重建支持新属性的数据库模式,接着运行 ant ctest 以创建样例数据。
发生了什么事
我们的 TRACK 表现在包含了一个用于保存 sourceMedia 属性的新字段。在创建好样例数据后就可以在这个表的内容中看到它的值(最简单的方法就是用 ant db 命令来执行查询,结果如图 6-1 所示)。
图 6-1 TRACK 表中的来源媒介信息
通过交叉检查为我们的持久化枚举类型赋值的代码,可以验证持久化保存到数据库的值是否正确。利用 Java 5 的 enum 功能,甚至可以让这种原始的查询显得更有意义。
为什么不能工作
在为我们的映射文档引入这些自定义类型的同时,也就在 build.xml 引入了另一种还没有反映出来过的新依赖。所以,如果一不小心,就会在运行 ant shema 命令之前,遇到 ant 编译失败的错误,收到一些 Hibernate 报告的以下信息:
[hibernatetool]INFO:Using dialect:org.hibernate.dialect.HSQLDialect
[hibernatetool]An exception occurred while running exporter#2:hbm2ddl
(Generates database schema)
[hibernatetool]To get the full stack trace run ant with-verbose
[hibernatetool]org.hibernate.MappingException:Could not determine type
for:com.oreilly.hh.StereoVolumeType, for columns:[org.hibernate.mapping
.Column(VOL_LEFT),org.hibernate.mapping.Column(VOL_RIGHT)]
BUILD FAILED
/Users/jim/Documents/Work/OReilly/svn_hibernate/current/examples/ch07/build
.xml:81:org.hibernate.MappingException:Could not determine type for:com
.oreilly.hh.StereoVolumeType, for columns:[org.hibernate.mapping.Column
(VOL_LEFT),org.hibernate.mapping.Column(VOL_RIGHT)]
Total time:3 seconds
这是因为,还没有编译我们新的自定义类型,Hibernate 不能发现或使用它们,所以映射起不到作用。作为一种快速修复措施,只要先运行 ant compile,再试着运行 ant schema 就可以了。也可以在 build.xml 中修复这个问题,这样以后就不会再麻烦任何人了:
<!--Generate the schemas for all mapping files in our class tree-->
<target name="schema"depends="compile"
description="Generate DB schema from the O/R mapping files">
……
compile 构建目标出现在 schema 后面也没有什么关系,Ant 会安排好它们之间的依赖关系。如果你觉得不习惯,可以任意交换它们的位置。为了稳妥起见,我们也可以让 compile 依赖于 codegen,以确保在试图编译什么以前,先生成数据类:
<!--Compile the java source of the project-->
<target name="compile"depends="codegen"
description="Compiles all Java classes">
……
有了声明好的这些依赖,你就可以从空的源代码目录开始,一步操作就完成所有的代码生成和编译处理:
%ant compile
Buildfile:build.xml
prepare:
[copy]Copying 3 files to/Users/jim/svn/oreilly/hib_dev_2e/current
/examples/ch07/classes
usertypes:
[javac]Compiling 2 source files to/Users/jim/svn/oreilly/hib_dev_2e
/current/examples/ch07/classes
codegen:
[hibernatetool]Executing Hibernate Tool with a Standard Configuration
[hibernatetool]1.task:hbm2java(Generates a set of.java files)
compile:
[javac]Compiling 8 source files to/Users/jim/svn/oreilly/hib_dev_2e
/current/examples/ch07/classes
BUILD SUCCESSFUL
Total time:3 seconds
好,我们回头再继续学习自定义类型吧。
为了能够看到更友好的提示信息(顺便也测试一下自定义持久化辅助工具的部分检索功能),我们可以扩充一下查询测试,让它为检索回的曲目打印输出这个属性关联的描述。必要的修改如例 6-5 中粗体字部分所示。
例 6-5:在 QueryTest.java 中显示来源媒介信息
……
//Print the tracks that will fit in seven minutes
List tracks=tracksNoLongerThan(Time.valueOf("00:07:00"),
session);
for(ListIterator iter=tracks.listIterator();
iter.hasNext();){
Track aTrack=(Track)iter.next();
String mediaInfo="";
if(aTrack.getSourceMedia()!=null){
mediaInfo=",from"+
aTrack.getSourceMedia().getDescription();
}
System.out.println("Track:\""+aTrack.getTitle()+"\""+
listArtistNames(aTrack.getArtists())+
aTrack.getPlayTime()+mediaInfo);
……
增加了这些扩充的代码后,运行 ant qtest,其输出结果如例 6-6 所示。具有非空(non-null)来源媒介值的曲目后面会跟着一个"from",之后显示的就是该曲目相应的媒介描述信息。
例 6-6:方便阅读的来源媒介信息显示
……
qtest:
[java]Track:"Russian Trance"(PPK)00:03:30,from Compact Disc
[java]Track:"Video Killed the Radio Star"(The Buggles)00:03:49,
from VHS Videocassette tape
[java]Track:"Gravity's Angel"(Laurie Anderson)00:06:06,from
Compact Disc
[java]Track:"Adagio for Strings(Ferry Corsten Remix)"(William
Orbit, Ferry Corsten, Samuel Barber)00:06:35,from Compact Disc
[java]Track:"Test Tone 1"00:00:10
[java]Comment:Pink noise to test equalization
注意,如果我们决定在 QueryTest 中不自己处理曲目属性子集的格式化输出,而是要依靠 Track 类的 toString()方法,那么我们不需要对 QueryTest 进行任何修改就可以看到这种新的输出信息,虽然用数据库查询我们已经看到了同样的枚举名称列表的最简单版本。我们在映射文档中规定 toString()方法返回的结果应该包含 sourceMedia 属性值,该方法负责处理这个属性值。可以检查生成的 toString()方法的源代码以验证这一点,或者编写一段简单的程序来看看 toString()方法输出的结果是什么样的。当然,一种很好的策略就是修改 AlbumTest.java,让它在我们修改 Track 以后可以通过编译并运行。最简单的修改就是在 addAlbumTrack()方法中通过硬编码(hard-code)让每个曲目都来自 CD,如例 6-7 所示(JavaDoc 已经为这种只图简单的做法想好了理由)。
例 6-7:修改 AlbumTest.java,增加对曲目来源媒介的支持
/**
*Quick and dirty helper method to handle repetitive portion of creating
*album tracks.A real implementation would have much more flexibility.
*/
private static void addAlbumTrack(Album album, String title, String file,
Time length, Artist artist, int disc,
int positionOnDisc, Session session){
Track track=new Track(title, file, length, new HashSet<Artist>(),
new Date(),(short)0,SourceMedia.CD,
new HashSet<String>());
track.getArtists().add(artist);
//session.save(track);
album.getTracks().add(new AlbumTrack(track, disc, positionOnDisc));
}
修改好文件以后,运行 ant atest 命令,就会显示在 Album 的 toString()方法中生成的来源媒介信息:
[java]com.oreilly.hh.data.Album@ccad9c[title='Counterfeit e.p.'tracks='[
com.oreilly.hh.data.AlbumTrack@9c0287[track='com.oreilly.hh.data.Track@6a21b2[
title='Compulsion'sourceMedia='CD']'],com.oreilly.hh.data.AlbumTrack@aa8eb7
[track='com.oreilly.hh.data.Track@7fc8a0[title='In a Manner of Speaking'source
Media='CD']'],com.oreilly.hh.data.AlbumTrack@4cadc4[track='com.oreilly.hh.da
ta.Track@243618[title='Smile in the Crowd'sourceMedia='CD']'],com.oreilly.h
h.data.AlbumTrack@5b644b[track='com.oreilly.hh.data.Track@157e43[title='Gone'
sourceMedia='CD']'],com.oreilly.hh.data.AlbumTrack@1483a0[track='com.oreilly
.hh.data.Track@cdae24[title='Never Turn Your Back on Mother Earth'sourceMedia=
'CD']'],com.oreilly.hh.data.AlbumTrack@63dc28[track='com.oreilly.hh.data.Tra
ck@ae511[title='Motherless Child'sourceMedia='CD']']]']
没有做多少工作,我们就利用 Hibernate 扩展了能够支持持久化的类型安全的枚举类。在付出一定的努力之后,就可以像 Hibernate 支持的其他原生值类型一样,方便地对这些枚举类进行持久化。
如果以后不用写代码,Hibernate 中支持的原生类型可以直接利用 Java 5 支持的 enum 关键字,那就太好了。不过,对此我并不抱多少希望,因为 Java 5 问世已经有一段时间了。但是,就各种评论来说,这是一种“温和”(mild)的方法,Hibernate wiki 上还有其他可供选择的支持枚举类型的用户自定义类型的实现。
接下来,我们将介绍如何映射更加复杂和特殊的自定义类型,没有人会指望 Hibernate 能够为这么复杂的类型映射提供内建的支持。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论