返回介绍

编写数据访问对象

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

数据访问对象(Data Access Object, DAO)是一种常见的设计模式,用于分离应用程序业务逻辑代码和访问,处理数据库记录的代码。在更大型的体系结构中,DAO 层可以作为两个独立的体系层次的边界。我们以 Spring Framework 为背景来介绍 DAO 对象,以便让你对如何将 Hibernate 应用到整体系统架构中有所了解;同时,之所以要介绍 DAO 对象,也是因为在现实世界需要与数据库进行交互的系统中,DAO 对象代表了一种常见的模式。

什么是数据访问对象

如果有作者用 20 多页的篇幅来介绍 DAO 模式、它的优点和缺点,不免会让我感到吃惊。如果你看过 Sun J2EE 蓝图,它就用很多篇幅解释了如何使用工厂方法的 DAO 创建模式、如何在更大的方法中使用 DAO 模式以支持企业应用程序的开发。我们准备省略很多拘泥于形式的细节,而将 DAO 模式简单地概括为两点。DAO 模式如下:

·将所有持久化操作(创建、读取、更新、删除)归结到一个接口中,通常按数据表或对象类型进行组织。

·该接口具有多种可切换的实现,可以支持任意各类的持久化 API 和底层存储介质。

或者,一个更精简的定义是“DAO 将持久化的所有繁琐细节隐藏在接口之后”。

当使用像 Hibernate 这样的对象/关系映射(Object/Relational Mapping, ORM)框架时,通常是将数据库作为一套对象来处理。本书的示例涉及 3 个对象:Artist、Album、Track,与这些对象相应,我们打算创建 3 个 DAO 对象:ArtistDAO、AlbumDAO、TrackDAO。图 13-1 简单地演示了要创建的 ArtistDAO 的类图。

图 13-1 用数据访问对象来分离应用的业务逻辑和持久化代码

在这个图中,你的应用程序的业务逻辑表示为 YourClass 类,这个类有一个对 ArtistDAO 接口实例的引用,该接口定义了 4 个简单的方法:

Artist persist(Artist artist)

将一个 Artist 对象的状态保存到数据库中。根据 artist 参数的状态,这个方法将插入一个新记录行,或更新一个已有的记录行。这个方法的约定是检查 artist 参数的 id 属性:如果 id 属性是 null,就向 ARTIST 表插入一个新行;如果 id 属性不为 null,就更新数据库中的相应行。该方法返回一个持久化的 Artist 对象,换句话说,如果你为这个方法传递一个 id 属性为 null 的 Artist 对象,它会在数据库中创建一行新的记录,并返回一个 id 属性不为 null 的 Artist 对象,这时的 id 属性包含的就是新插入记录的标识符。

void delete(Artist artist)

从数据库中删除匹配的记录。

Artist uniqueByName(String name)

返回姓名等于 name 参数值的一个 Artist 对象。

Artist getArtist(String name, boolean create)

查找姓名匹配 name 参数值的 Artist 对象。如果 create 参数为 true,则当没有找到匹配的 Artist 对象时,该方法就用提供的 name 参数来创建并持久化一个新的 Artist 对象。

虽然在 YourClass 类的代码中引用的是 ArtistDAO 接口,但实际上调用的是 ArtistDAO 接口的一个实现—ArtistHibernateDAO。该 ArtistDAO 的实现类继承了 Spring 的 HibernateDaoSupport 类,这个 Spring 提供的工具类包含了所有必需的魔力,让 Hibernate 代码的编写变得尽可能简单。使用 HibernateDaoSupport,你可以不必编写前面几章中看到的所有异常处理、事务管理以及 session 管理代码。事实上,我想你一定感到惊讶(也可能有些失望),在 Spring 中使用 Hibernate 变得这么简单。

本书使用以下命名约定,DAO 接口在 com.oreilly.hh.dao 包中进行定义,它的类名由相关联的对象名称再附加上"DAO"组成(例如 ArtistDAO)。将该接口的 Hibernate 特定实现命名为 ArtistHibernateDAO,放在 com.oreilly.hh.dao.hibernate 包中。

为什么要使用 DAO

之所以要使用 DAO 有很多原因,但第一个也是最重要的原因是这种模式可以增加灵活性。因为是对接口进行编码,当使用了不同的 O/R 映射服务或存储介质时,就可以方便地构建新的实现。在前面的介绍中曾经提过,Spring 提供的集成层可以使用许多对象-关系映射技术,从 iBatis SQL Maps 到 Oracle 的 TopLink,再到 Hibernate,但 Spring 也提供了一套丰富的抽象,让用 JDBC 执行 SQL 变得相当直接。在我经历过的大多数应用程序开发中,Hibernate 提供了大部分持久化逻辑,但它并不能解决所有问题。有时当需要直接执行 SQL 时,直接使用 Spring 提供的 JDBCTemplate 这些类就非常方便。如果在应用程序的业务逻辑层和持久层之间插入 DAO 接口,这样在需要的时候,就可以更容易地切换到其他特定 DAO 类或方法的实现。这种灵活性和分离性的优点体现在两个方面,其一是可以容易地替换特定 DAO 类的实现,其二是当需要重写或更新应用程序的业务逻辑时,可以重用已有的持久化层。本书的许多读者现在遇到的情形是:开发团队一直在维护一个用 Struts 1.x 编写的遗留系统,而你想将应用程序升级到 Stripes 或 Struts 2。如果持久化代码紧密地耦合到了 Web 框架代码中,你可能会发现如果不重写其中的一方面,就不可能重写另一方面。

对于这么简单的一个应用程序,采用 DAO 模式似乎显得没有必要,但是更经常遇到的情况是,你需要在多个项目中重用持久化代码。DAO 对象看起来似乎与敏捷开发相背离,但这种在复杂度上的付出会随着时间的推移而体现出其价值。如果你的系统不是那种简单的"Hello World"例子,就可能需要花些时间将持久化逻辑从应用程序的业务逻辑中分离出来。

编写 ArtistDAO 接口

不耽误时间了,我们看看这个示例的代码编写。需要做的第一件事就是编写 ArtistDAO 接口。在 com.oreilly.hh.dao 包中创建一个名为 ArtistDAO 的接口,并将例 13-2 所示的代码放到这个新接口中。

例 13-2:ArtistDAO 接口

package com.oreilly.hh.dao;

import com.oreilly.hh.data.Artist;

/**

*Provides persistence operations for the Artist object

*/

public interface ArtistDAO{

/**

*Persist an Artist instance(create or update)

*depending on the value of the id

*/

public Artist persist(Artist artist);

/**

*Remove an Artist from the database

*/

public void delete(Artist artist);

/**

*Return an Artist that matches the name argument

*/

public Artist uniqueByName(String name);

/**

*Returns the matching Artist object.If the

*create parameter is true, this method will

*insert a new Artist and return the newly created

*Artist object.

*/

public Artist getArtist(String name, boolean create);

}

好,这段代码看起来相对比较容易,这里我们只要介绍几个简单方法的实现。接下来就看看如何使用 HibernateDaoSupport 类来实现其处理逻辑。

实现 ArtistDAO 接口

下一步就应该编写 ArtistDAO 的实现。我们打算编写一个 ArtistHibernateDAO 类来实现 ArtistDAO 接口,并继承 Spring 的 HibernateDaoSupport 类。HibernateDaoSupport 提供了对 Hibernate Session 对象和 HibernateTemplate 的访问,可以用 HibernateTemplate 来简化对 Hibernate Session 对象任意的操作。为了实现 ArtistDAO,先在 com.oreilly.hh.dao.hibernate 包中创建一个新类,让它包含例 13-3 所示的 ArtistDAO 接口的特定于 Hibernate 的实现。

例 13-3:实现 ArtistDAO 接口

package com.oreilly.hh.dao.hibernate;

import java.util.HashSet;

import org.apache.log4j.Logger;

import org.hibernate.Query;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.oreilly.hh.dao.ArtistDAO;

import com.oreilly.hh.data.Artist;

import com.oreilly.hh.data.Track;

/**

*Hibernate-specific implementation of the ArtistDAO interface.This class

*extends the Spring-specific HibernateDaoSupport to provide access to

*the SessionFactory and the HibernateTemplate.

*/

public class ArtistHibernateDAO extends HibernateDaoSupport

implements ArtistDAO{

private static Logger log=

Logger.getLogger(ArtistHibernateDAO.class);

/*(non-Javadoc)

*@see com.oreilly.hh.dao.ArtistDAO#persist(com.oreilly.hh.data.Artist)

*/

public Artist persist(Artist artist){❶

return(Artist)getHibernateTemplate().merge(artist);

}

/*(non-Javadoc)

*@see com.oreilly.hh.dao.ArtistDAO#delete(com.oreilly.hh.data.Artist)

*/

public void delete(Artist artist){❷

getHibernateTemplate().delete(artist);

}

/*(non-Javadoc)

*@see com.oreilly.hh.dao.ArtistDAO#uniqueByName(java.lang.String)

*/

public Artist uniqueByName(final String name){❸

return(Artist)getHibernateTemplate().execute(new HibernateCallback(){

public Object doInHibernate(Session session){

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

query.setString("name",name);

return(Artist)query.uniqueResult();

}

});

}

/*(non-Javadoc)

*@see com.oreilly.hh.dao.ArtistDAO#getArtist(java.lang.String, boolean)

*/

public Artist getArtist(String name, boolean create){❹

Artist found=uniqueByName(name);

if(found==null&&create){

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

found=persist(found);

}

if(found!=null&&found.getActualArtist()!=null){

return found.getActualArtist();

}

return found;

}

}

❶这个 persist()方法只是简单地调用 HibernateTemplate 的 merge()方法。merge()方法的实现会检查 artist 参数的 id 属性。如果 id 为 null, merge()方法就向 ARTIST 表插入一行新的记录,并返回一个带有生成的 id 属性的 Artist 新实例。如果 id 属性不为 null, merge()方法就会先查找匹配的记录行,再用 artist 参数的内容来更新这一记录。

❷delete()只是将 artist 参数传递给 HibernateTemplate 的 delete()方法。该方法需要接收一个其 id 属性不为 null 的对象,并删除 ARTIST 表中的相应数据行。

❸uniqueByName()才是更有趣的开始。此处是我们在这个类中第一次引用 Session 对象,在整本书中曾经一直在使用它。首先用 getSession()获取一个 NamedQuery。接着再设置命名参数 name,取回一个惟一的查询结果。如果数据库中没有匹配的 Artist, uniqueResult()将返回 null。相信你也注意到这里使用了一个匿名的 HibernateCallback 实例,并将它传递给 HibernateTemplate 对象。有关 HibernateCallback 类的更多信息,可以参阅本章后面的 13.2.6 节。

❹getArtist()方法实际上只是将查询转交给了 ArtistDAO 中的其他方法。它通过调用 uniqueByName(),按照名称来取回一个 Artist 对象。如果没有找到任何 Artist 对象,而且 create 参数为 true, getArtist()方法就会创建一个 id 属性为 null 的 Artist 实例,再调用 persist()方法。如果没有找到匹配的 Artist 对象,而且 create 参数为 false,这个方法就会返回 null。如果找到的或新创建的 Artist 对象具有一个非 null 的 actualArtist 属性,这个方法将返回 artist.getActualArtist()的值。(这一步骤的目的可以参阅第 5.5 节的解释。)

HibernateDaoSupport 的功能

HibernateDaoSupport 可以让我们连接到 SessionFactory,但不必知道 Hibernate 环境是怎么创建或配置的。当将 HibernateDaoSupport 子类化(subclass)到我们的 DAO 类后,就可以通过 getSession()方法访问 Hibernate Session,通过 getHibernateTemplate()方法访问 Hibernate-Template。你应该已经知道用 Hibernate Session 对象可以做什么(就是本书前面 10 章的内容)。Spring/Hibernate 集成中有趣的部分是由 HibernateTemplate 类提供的,我们接下来就深入研究一下这个类的细节。

根据 HibernateTemplate 的 JavDoc 中的说明:“可以用这个类来取代对原始 Hibernate 3 Session API 的使用。”HibernateTemplate 简化了原本用 Session 对象完成的任务,同时将 Hibernate-Exception 转换为多个通用的 DataAccessException。可以用两种方法来使用 HibernateTemplate:调用一套简单的工具函数,例如 load()、save()、delete();或者使用 execute()方法来执行一个 HibernateCallback 实例的调用。在大多数情况下,使用 HibernateTemplate 的简单工具函数就足够了;只有要在 HibernateTemplate 内执行某些 Hibernate 特定的代码时,才需要创建一个 HibernateCallback 对象。

DataAccessException 是什么?在前面介绍 DAO 设计模式时,就说过它的目的就是要向应用程序代码屏蔽属于任何持久化 API 或库的特殊性。如果我们独立于特定技术的 DAO 抛出一个 Hibernate 特定的 ObjectNotFoundException 异常,就对我们没什么帮助了。所以 Hibernate-Template 就需要负责处理可能在其中发生的任何 Hibernate 特定的异常。Stripes API 通过将这些特定实现的异常封装到它自己通用的数据访问异常中,提供了一种对这种异常进行包容的简单方法。

HibernateTemplate 和 HibernateCallback 是帮助我们避免编写一行又一行不必要的 Java 代码的实际骨干。接下来就使用它们两个来重新实现前面几章中的示例。

应该怎么做

在使用 HibernateTemplate 和 HibernateCallback 之前,我们需要先大致看看它们提供的方法。HibernateTemplate 提供了很多简单方便的方法,可以将原来直接使用 Hibernate Session API 时需要多行代码才能完成的功能压缩到只需要简单的一行。我们以查询数据库表为例,来看看这种简单方法到底是怎么回事。例 13-4 演示了查询和搜索对象的几个示例。

例 13-4:HibernateTemplate 的 find()工具方法

HibernateTemplate tmpl=getHibernateTemplate();

//All of these lines Find Artist with name'Pavement'

List artists=tmpl.find("from com.oreilly.hh.data.Artist a"+

"where a.name='Pavement'");❶

String name="Pavement";

List artists=tmpl.find("from com.oreilly.hh.data.Artist a"+

"where a.name=?",name);❷

List artists=tmpl.findByNamedParam("from com.oreilly.hh.data.Artist a"+

"where a.name=:name","name",name);❸

//Assuming that there is a NamedQuery annotation"Artist.byName"on the

//Artist class

List artists=tmpl.findByNamedQuery("Artist.byName",name);❹

Artist artist=new Artist();

artist.setName("Pavement");

List artists=tmpl.findByExample(artist);❺

//If we want to iterate through the result

Iterator artists=tmpl.iterate("from com.oreilly.hh.data.Artist"+

"where a.name=?",name);❻

//The following lines find all Artists

List artists=tmpl.find("from com.oreilly.hh.data.Artist");❼

List artists=tmpl.loadAll(Artist.class);

Find 方法相对比较直接:

❶这是一个简单的 find()方法,接受一个不带参数的 HQL 查询语句。

❷该方法的这个版本接受一个 HQL 查询语句,以及一个附加的参数。类似地另一个版本将接受一个查询语句和一组附加的参数:List find(String hql, Object[]params)。这些方法都支持非命名查询参数的使用,但正如第 3 章所述,编写查询还有更好的方法。

❸findByNamedParameter()方法可以处理带有命名参数的查询。

❹findByNamedQuery()方法可以让你快速调用一个预定义的 HQL 查询,在这个例子中是名为"Artist.byName"的查询。

❺通过 findByExample()方法,还可以使用 Hibernate 的示例查询(query-by-example)的功能。

❻如果想循环遍历查询结果,可以调用 iterate()方法。当调用 iterate()方法时,Hibernate 首先取回匹配数据行的所有 ID,当通过返回的 Iterator 遍历结果时,再初始化各个元素。

❼最后,如果只是想加载给定某个表的所有数据行,则可以调用 find()或 loadAll()方法。

正如第 3.4 节所讨论的,命名查询是一种将查询定义移出 DAO 代码的好办法。如果你使用的是标注,可以使用 @NamedQuery 标注来定义命名查询。有关该标注的更多细节可以参阅第 7 章的相关内容。

如果我们已经知道了某个持久对象的 ID 值,就可以用 HibernateTemplate 提供的工具方法通过 ID 来加载对象,如例 13-5 所示。

例 13-5:用 HibernateTemplate 加载对象

//Identifier of Artist to load

Integer id=1;

//Load an Artist object, return persistent Artist object

Artist artist=getHibernateTemplate().load(Artist.class, id);

//Populate the object passed in as a parameter.Using the

//object's type to specify the class

Artist artist=new Artist();

getHibernateTemplate().load(artist, id);

在第 1 个例子中,我们用一个 Class 和序列化 ID 值来调用 load()函数。Hibernate 将从数据库中检索对应的记录,并返回请求对象的实例。除了使用 Class 对象,你也可以传递一个对象实例,Hibernate 会用参数的类型来决定检索哪个类。

我们已经看过了 HibernateTemplate 中用于查询和加载对象的工具方法。那么怎样修改数据库中的记录呢?例 13-6 演示了几个在数据库中插入和更新记录的示例。

例 13-6:用 HibernateTemplate 保存和更新记录

//Persist a new instance in the database

Artist a=new Artist();

a.setName("Fischerspooner");

getHibernateTemplate().save(a);

//Load, modify, update a row in the database

Artist a=getHibernateTemplate().load(Artist.class,1);

a.setName("Milli Vanilli");

getHibernateTemplate().update(a);

//Either insert or update depending on the identifier

//of the object;associate resulting object with Session

Artist a=getHibernateTemplate().merge(a);

Save()和 update()方法也比较直观,这两个方法与 Hibernate Session 对象上的同名方法类似。Save()方法生成一个新的 ID,并向数据表中插入一行新的记录;update()方法则更新数据库表中的匹配记录。merge()方法更灵活些:它会检查参数的 id 属性,按照 ID 是否为 null 来调用 save()或 update()方法。

也可以通过 HibernateCallback,使用 Hibernate Session 来执行任何 SQL 语句。在详细解释这一用法之前,我们先看看例 13-7。

例 13-7:编写 HibernateCallback

final String name="Pavement";

Artist artist=(Artist)getHibernateTemplate().execute(new HibernateCallback(){

public Object doInHibernate(Session session){

Criteria criteria=session.createCriteria(Artist.class);

criteria.add(Restrictions.like("name",name));

return criteria.uniqueResult();

}

});

那么这个例子中到底发生了什么?这个示例首先实例化一个实现了 HibernateCallback 接口的匿名内部类,并将它传递给 HibernateTemplate 的 execute()方法。HibernateCallback 接口只定义了一个方法 doInHibernate(),需要向它传递一个 Hibernate Session。在这个方法的内部(在我们的匿名内部类中实现的)使用 Hibernate Criteria API 生成查询,按姓名检索回一个 Artist 对象。

为什么当我们本来能够容易地获得 Hibernate Session 的引用,而且也能创建同样的 Criteria 对象时,却要使用回调方法?即使在 HibernateDaoSupport 中使用 getSession()方法能够直接访问 Session 对象,我们还是希望避免直接调用 Hibernate API,因为我们不想抛出任何 Hibernate 特定的异常(甚至也不想抛出 RuntimeException)。需要记住,你的应用程序正在通过一个接口来访问这个 DAO,它不知道也不关心 Hibernate 特定的 ObjectNotFoundException 或 HQL 中的异常。不是直接用 getSession()访问 Session 对象,你能够也应该向你的应用程序屏蔽这些底层处理细节,这就是为什么要在 HibernateTemplate 中用 HibernateCallback 来运行 Hibernate API 调用的原因了。

其他 DAO 在哪里

因为这些 DAO 处理都非常相似,所以不值得在这里一一列出和讨论其他 DAO 处理了。你可能不想手工输入所有代码,如果还想看看它们的话,只要下载代码示例就可以了。

发布评论

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