返回介绍

集合的持久化

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

我们的第一个任务就是改进 CreateTest 类:利用数据库模式中新增的内容,创建一些艺人对象,并为它们关联上一些曲目对象。

应该怎么做

首先,在 CreateTest.java 中增加一些辅助方法,以简化我们的任务,如例 4-7 所示(修改和新增内容以粗体显示)。

例 4-7:用于查找和创建艺人对象,并将它们链接到曲目对象的工具方法

package com.oreilly.hh;

import org.hibernate.*;

import org.hibernate.cfg.Configuration;

import com.oreilly.hh.data.*;

import java.sql.Time;

import java.util.*;❶

/**

*Create more sample data, letting Hibernate persist it for us.

*/

public class CreateTest{

/**

*Look up an artist record given a name.

*@param name the name of the artist desired.

*@param create controls whether a new record should be created if

*the specified artist is not yet in the database.

*@param session the Hibernate session that can retrieve data

*@return the artist with the specified name, or<code>null</code>if no

*such artist exists and<code>create</code>is<code>false</code>.

*@throws HibernateException if there is a problem.

*/

public static Artist getArtist(String name, boolean create, Session session)❷

{

Query query=session.getNamedQuery("com.oreilly.hh.artistByName");

query.setString("name",name);

Artist found=(Artist)query.uniqueResult();❸

if(found==null&&create){❹

found=new Artist(name, new HashSet<Track>());

session.save(found);

}

return found;

}

/**

*Utility method to associate an artist with a track

*/

private static void addTrackArtist(Track track, Artist artist){❺

track.getArtists().add(artist);

}

和处理 Hibernate 经常使用的方法一样,这段代码相当简单,一目了然:

❶我们前面导入过 java.util.Date,但现在需要导入整个 util 包,才能使用 Collection 接口。粗体的“*”就是为了突出这一点,不过浏览例子时容易忽略它。

❷如果我们为同样的艺人创建多个曲目的话,则希望可以重用同样的数据(这就是使用 Artist 对象,而不只是存储字符串的全部原因)。getArtist()方法完成按名字查找艺人的功能。

❸uniqueRusult()方法是 Query 接口的一个方便特色,尤其适合于以下情况:我们知道查询要么只有一个结果,要么没有任何结果。这样就免去了获取结果列表、检查列表长度。如果包含数据的话,再提取第一个结果元素,这么多繁琐的步骤。使用这个方法要么只取回一个结果;或者当没有结果时,就返回 null(如果查询返回多个结果,这个方法将抛出一个异常。你可能会认为我们在列上施加的 unique 约束能够防止这种异常,但 SQL 是大小写敏感的,而我们的查询匹配是大小写不敏感的。所以在创建一个新记录之前,应该总是先调用 getArtist(),以确保同名的艺人是否已经存在)。

❹所以我们需要做的就是检查 null 值,如果没有找到相应名字的艺人,而且 create 标志也表明我们想要创建艺人对象时,就创建一个新的 Artist。

如果我们省去 session.save()调用,那么所有艺人 Artist 对象将保持瞬时状态。Hibernate 这时也很有帮助,如果我们试图在这种情况下提交事务,Hibernate 就会检查到持久化 Track 实例引用了瞬时状态的 Artist 实例,从而抛出一个异常。你可以回顾一下第 3 章对生命周期的讨论,以及第 5 章的“生命周期关联”,它们更深入地探究了这一问题。

❺addTrackArtist()方法差不多简单得令人尴尬。它只是普通的 Java Collection 代码,获取属于某个 Track 的艺人对象集合(Set),再将指定的 Artist 添加到这个集合中。这样真可以实现我们需要的所有功能吗?我们通常不得不写的数据库处理代码都跑到哪儿去了?欢迎来到对象/关系映射工具的精彩世界!

可以看到 getArtist()方法内部使用一个命名查询来检索 Artist 记录。我们在 Artist.hbm.xml 的末尾添加了这个命名查询的定义,如例 4-8 所示。(实际上,可以将命名查询放在任意映射文件中,但这是最合适的地方,因为这个查询和 Artist 记录相关。)

例 4-8:在 Artist 映射文档中添加检索查询语句

<query name="com.oreilly.hh.artistByName">

<![CDATA[

from Artist as artist

where upper(artist.name)=upper(:name)

]]>

</query>

该命名查询使用 upper()函数对艺人的姓名进行大小写不敏感(case-insensitive)的比较,这样,即使查询时所用的大小写与数据库中保存的数据不一样,也可以检索到相应的艺人。这种不区分大小写,但又能保留其原有大小写的方法是一种用户友好的方法,用户都喜欢这种实现方式,所以值得我们尽可能实现。除了 HSQLDB,其他数据库中将字符串转换成大写的函数可能有不同的名称,但应该都有。我们将在第 8 章介绍一种面向 Java 的、独立于数据库的方法,以一种漂亮的方式来实现这种字符串转换。

现在,我们以此为基础来真正创建一些链接了艺人的曲目对象。例 4-9 展示了 CreateTest 类的剩余部分,新增部分以粗体字显示。按照示例所演示的,编辑你的源文件(或直接下载以节省打字时间)。

例 4-9:修改 CreateTest.java 的 main()方法,增加艺人关联数据

public static void main(String args[])throws Exception{

//Create a configuration based on the XML file we've put

//in the standard place.

Configuration config=new Configuration();

config.configure();

//Get the session factory we can use for persistence

SessionFactory sessionFactory=config.buildSessionFactory();

//Ask for a session using the JDBC information we've configured

Session session=sessionFactory.openSession();

Transaction tx=null;

try{

//Create some data and persist it

tx=session.beginTransaction();

Track track=new Track("Russian Trance",

"vol2/album610/track02.mp3",

Time.valueOf("00:03:30"),

new HashSet<Artist>(),❶

new Date(),(short)0);

addTrackArtist(track, getArtist("PPK",true, session));

session.save(track);

track=new Track("Video Killed the Radio Star",

"vol2/album611/track12.mp3",

Time.valueOf("00:03:49"),new HashSet<Artist>(),

new Date(),(short)0);

addTrackArtist(track, getArtist("The Buggles",true, session));

session.save(track);

track=new Track("Gravity's Angel",

"vol2/album175/track03.mp3",

Time.valueOf("00:06:06"),new HashSet<Artist>(),

new Date(),(short)0);

addTrackArtist(track, getArtist("Laurie Anderson",true, session));

session.save(track);

track=new Track("Adagio for Strings(Ferry Corsten Remix)",❷

"vol2/album972/track01.mp3",

Time.valueOf("00:06:35"),new HashSet<Artist>(),

new Date(),(short)0);

addTrackArtist(track, getArtist("William Orbit",true, session));

addTrackArtist(track, getArtist("Ferry Corsten",true, session));

addTrackArtist(track, getArtist("Samuel Barber",true, session));

session.save(track);

track=new Track("Adagio for Strings(ATB Remix)",

"vol2/album972/track02.mp3",

Time.valueOf("00:07:39"),new HashSet<Artist>(),

new Date(),(short)0);

addTrackArtist(track, getArtist("William Orbit",true, session));

addTrackArtist(track, getArtist("ATB",true, session));

addTrackArtist(track, getArtist("Samuel Barber",true, session));

session.save(track);

track=new Track("The World'99",

"vol2/singles/pvw99.mp3",

Time.valueOf("00:07:05"),new HashSet<Artist>(),

new Date(),(short)0);

addTrackArtist(track, getArtist("Pulp Victim",true, session));

addTrackArtist(track, getArtist("Ferry Corsten",true, session));

session.save(track);

track=new Track("Test Tone 1",❸

"vol2/singles/test01.mp3",

Time.valueOf("00:00:10"),new HashSet<Artist>(),

new Date(),(short)0);

session.save(track);

//We're done;make our changes permanent

tx.commit();

}catch(Exception e){

if(tx!=null){

//Something went wrong;discard all partial changes

tx.rollback();

}

throw new Exception("Transaction failed",e);

}finally{

//No matter what, close the session

session.close();

}

//Clean up after ourselves

sessionFactory.close();

}

}

例 4-9 对现有程序代码的修改相当有限:

❶前面几行代码用于创建第 3 章中的 3 个曲目对象,这里只需要为每个曲目对象提供一个新的参数来作为 Artist 关联的最初空集合。随后每个再用一行代码来为曲目建立艺人的关联。我们原本可以用不同的结构来实现这段代码,编写一个工具方法以创建包含艺人对象的最初的 HashSet,这样就能用一行代码完成所有的处理。不过,对于多艺人的曲目对象,我们实际使用的这种方法的适应性更好,下一节将演示这种情况。

❷最大一段新代码是简单地添加了 3 个新的曲目,以演示如何处理每个曲目有多个关联艺人的情况。如果你喜欢电子音乐和舞曲(或者古典音乐之类的曲目),就应该知道这个问题多么重要。因为我们将链接表达为集合对象,所以维护关联就简化为将每个艺人对象添加到相应的曲目对象就可以了。

❸最后,我们添加了一个没有艺人关联的曲目对象,看看会发生什么。现在,你可以运行 ant ctest 来创建新的样例数据,其中包含了曲目、艺人以及他们之间的关联。

如果需要对测试数据创建程序进行修改,又想再次从空数据库开始运行程序,一个有用的技巧是执行 ant schema ctest 命令。这个命令是告诉 Ant 先后分别执行 schema 和 ctest 构建目标。执行 schema 构建目标会将现有数据全部清空,接着再执行 ctest 以重建数据。

注意:当然,现实生活中将数据放到数据库会采用其他做法—通过用户界面或将实际的曲目数据直接导入数据库。不过,如果只是单元测试,代码看起来就是这个样子。

发生了什么事

运行 ctest 后,除了 Hibernate 使用的 SQL 语句(如果仍然将 hibernate.cfg.xml 中的 show_sql 设置为 true),没有什么非常有意义的输出。可以打开 data/music.script 来看一看里面新创建了什么,或者用 ant db 命令打开数据库管理器图形界面来查看。看看这三个数据库表的内容。图 4-2 显示了连接表中代表艺人和曲目之间关联的结果。原始数据已经隐藏起来了。如果你习惯使用关系数据库模型,则这样的查询结果表示一切都运行正常;如果你和我一样都是凡人,那么下一节介绍的内容将更加令人信服,当然也更加有趣。

图 4-2 新版 CreateTest 创建的艺人和曲目之间的关联

发布评论

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