9.3 DAO组件层
业务逻辑层组件依赖于持久层组件,而持久层组件则提供数据表的基本CRUD操作。通过持久层组件,使业务逻辑层组件的实现与特定数据库访问分离,从而提高系统的解耦。
9.3.1 DAO组件的结构
通过第8章关于DAO模式的介绍可知,DAO模式需要为每个DAO组件编写DAO接口,同时至少提供一个实现类,根据不同需要,可能有多个实现类。
为了让逻辑组件与具体的DAO组件分离,还必须有一个DAO工厂。
图9.2是本系统的DAO组件接口的类图。图9.3是本系统系统DAO组件实现类的类图。此处的实现类都是基于Hibernate的实现。

图9.2 DAO组件接口的类图
|
图9.3 DAO组件实现类的类图
9.3.2 编写DAO接口
DAO接口包类里有5个DAO,其中DAO是一个具有一般性的DAO接口,其他DAO接口都继承这个DAO。这里选择DAO与NewsDAO作为示例。
DAO接口的代码如下:
public interface DAO
{
//返回全部的物品
public List getObjects(Class class);
//根据id返回某个持久化类
public Object getObject(Class class, Serializable id);
//保存持久化类
public void saveObject(Object o);
//删除某个持久化类
public void removeObject(Class class, Serializable id);
}
下面对各个方法进行简要说明。
getObject是获取所有该类型对象的方法。输入参数是某个类的类型,相当于取出一个表所有行的sql语句。
getObject封装的是读取单条记录的操作。
saveObject封装的是插入或更新单条记录的操作。插入或更新取决于该对象是否已被持久化过。
removeObject封装的是删除单条记录的操作。
由此可见,DAO的实质就是对数据表的CRUD原子操作。
一般的DAO的实现要求用到Java的反射机制,因此对于初学者不容易实现。但实现之后可以通过实现类对任何PO进行操作。通常,我们用每个类各自的DAO对该类进行CRUD操作。
NewsDAO接口的代码如下:
public interface NewsDAO extends DAO
{
//根据id返回News持久化类
public News getNews(Long newsId);
//保存消息
public void saveNews(News news);
//删除消息
public void removeNews(Long newsId);
}
可以看到NewsDAO里并没有封装取出所有News的操作,其实封不封装该操作取决于业务需求。在这个系统里面没有取出所有News的需求,因此可以不封装。如果要考虑扩展系统的话,也可以先封装,在扩展的时候使用。
注意:接口的使用在这里起了很重要的作用。接口就是定义与实现的分离,在一个面向对象的系统中,其功能是由很多对象协作完成。各个对象是如何实现的,对系统设计人员来讲是透明的;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都要着重考虑,这也是系统设计的主要工作内容。然而通过多态引入接口可以降低对象之间的依赖,解除耦合。很多经典的设计模式都离不开接口,因此,更深层次理解接口是提高技术水平的必不可少的步骤,有兴趣的读者可以参考相关设计模式方面的书籍。
下面依次是UserDAO、CategoryDAO及NewsReviewDAO的源代码:
UserDAO的源代码:
public interface UserDAO extends DAO {
/**
* 根据用户名获取User
* @param username 需要访问的用户名
* @return 用户名对应的用户
*/
public User getUser(String username);
/**
* 保存用户
* @param 需要保存的用户
*/
public void saveUser(User user);
/**
* 根据用户名,删除用户,
* @param username 需要删除用户的用户名
*/
public void removeUser(String username);
CategoryDAO的源代码:
public interface CategoryDAO extends DAO {
/**
* 根据id获取种类
* @parameter categoryId 需要加载的种类id
* @return 种类id对应的种类
*/
public Category getCategory(Long categoryId);
/**
* 保存消息分类
* @parameter category需要保存的消息分类
*/
public void saveCategory(Category category);
/**
* 根据消息id删除消息分类
* @ categoryId 需要删除的消息分类id
*/
public void removeCategory(Long categoryId);
/**
* 获取全部消息分类
* @return 返回数据库中全部消息分类
*/
public List getCategories();
}
NewsReviewDAO的源代码:
public interface NewsReviewDAO extends DAO{
/**
* 根据回复id获取消息回复
* @return id对应的消息回复
*/
public NewsReview getNewsReview(Long newsReviewId);
/**
* 保存特定的消息回复
* @parameter newsReview需要保存的消息回复。
*/
public void saveNewsReview(NewsReview newsReview);
/**
* 根据回复id删除消息回复
* @parameter newsReviewId需删除的消息回复id
*/
public void removeNewsReview(Long newsReviewId);
}
9.3.3 编写DAO的具体实现
编写DAO的具体实现在这里也很简单,下面是NewsDAO接口的Hibernate实现的源代码:
NewsDAOHibernate继承BaseDAOHibernate,实现了NewsDAO
public class NewsDAOHibernate extends BaseDAOHibernate
implements NewsDAO
{
//根据主键查找News持久化对象
public News getNews(Long id)
{
News news = (News) getHibernateTemplate().get(News.class, id);
//如果不能加载该持久化对象,抛出异常
if (news == null)
{
throw new ObjectRetrievalFailureException(News.class, id);
}
return news;
}
//保存消息
public void saveNews(News news)
{
getHibernateTemplate().saveOrUpdate(news);
}
//根据主键删除消息
public void removeNews(Long id)
{
getHibernateTemplate().delete(getNews(id));
}
}
NewsDAO的Hibernate实现继承了BaseDAOHibernate,下面是BaseDAOHibernate的源代码:
public class BaseDAOHibernate extends HibernateDaoSupport
implements DAO
{
//提供日志功能
protected final Log log = LogFactory.getLog(getClass());
//保存对象
public void saveObject(Object o)
{
getHibernateTemplate().saveOrUpdate(o);
}
//根据持久化类的类,主键获取对象
public Object getObject(Class clazz, Serializable id)
{
Object o = getHibernateTemplate().get(clazz, id);
//如果没有获得该对象
if (o == null)
{
throw new ObjectRetrievalFailureException(clazz, id);
}
return o;
}
//查找某个持久化类的全部实例,查找到数据库对应表的全部记录
public List getObjects(Class clazz)
{
return getHibernateTemplate().loadAll(clazz);
}
//根据主键,删除某个持久化类的特定实例,对应删除数据库的特定记录
public void removeObject(Class clazz, Serializable id)
{
getHibernateTemplate().delete(getObject(clazz, id));
}
}
BaseDAOHibernate就是前面所说的一般DAO接口的实现,里面附加了日志(log)功能,增强了系统的可维护性和可管理性。这里的DAO实现都很简洁,只需要简单的一行代码就分别实现CRUD操作。这得益于Spring的强大功能:Spring提供了HibernateDaoSupport工具类以及HibernateTemplate,其中HibernateTemplate允许以模板化方式的操作访问数据库。
注意:前面提到DAO需要封装数据源,而在这个DAO里面我们感觉不到数据源的存在,这也得益于Spring和Hibernate的封装,对数据源的访问放在配置文件里管理。
下面依次是UserDAO、CategoryDAO和NewsReviewDAO实现类的源代码。这些实现类都基于Spring的Hibernate支持,因此非常简单。
UserDAOHibernate的源代码如下:
public class UserDAOHibernate extends BaseDAOHibernate
implements UserDAO
{
/**
* 根据用户名获取User
* @param username 需要访问的用户名
* @return 用户名对应的用户
*/
public User getUser(String username) {
User user = (User) getHibernateTemplate().get(User.class, username);
if(user == null){
log.warn("user '" + username + "' not found...");
}
return user;
}
/**
* 保存用户
* @param 需要保存的用户
*/
public void saveUser(final User user) {
if (log.isDebugEnabled()) {
log.debug("user's id: " + user.getUsername());
}
getHibernateTemplate().saveOrUpdate(user);
// necessary to throw a DataIntegrityViolation and catch it in UserManager
getHibernateTemplate().flush();
}
/**
* 根据用户名,删除用户,
* @param username 需要删除用户的用户名
*/
public void removeUser(String username) {
getHibernateTemplate().delete(getUser(username));
}
}
CategoryDAOHibernate的源代码如下:
public class CategoryDAOHibernate extends BaseDAOHibernate
implements
CategoryDAO
{
/**
* 根据id获取种类
* @parameter categoryId 需要加载的种类id
* @return 种类id对应的种类
*/
public Category getCategory(Long id) {
Category category = (Category) getHibernateTemplate().get(
Category.class, id);
if (category == null) {
throw new ObjectRetrievalFailureException(Category.class, id);
}
return category;
}
/**
* 保存消息分类
* @parameter category需要保存的消息分类
*/
public void saveCategory(Category category) {
getHibernateTemplate().saveOrUpdate(category);
}
/**
* 根据消息id删除消息分类
* @ categoryId 需要删除的消息分类id
*/
public void removeCategory(Long id) {
getHibernateTemplate().delete(getCategory(id));
}
/**
* 获取全部消息分类
* @return 返回数据库中全部消息分类
*/
public List getCategories() {
return getHibernateTemplate().find("from Category");
}
}
NewsReviewDAOHibernate的源代码如下:
public class NewsReviewDAOHibernate extends BaseDAOHibernate
implements
NewsReviewDAO
{
/**
* 根据回复id获取消息回复
* @return id对应的消息回复
*/
public NewsReview getNewsReview(Long id) {
NewsReview newsReview = (NewsReview) getHibernateTemplate().get(
NewsReview.class, id);
if (newsReview == null) {
throw new ObjectRetrievalFailureException(NewsReview.class, id);
}
return newsReview;
}
/**
* 保存特定的消息回复
* @parameter newsReview需要保存的消息回复。
*/
public void saveNewsReview(NewsReview newsReview) {
getHibernateTemplate().saveOrUpdate(newsReview);
}
/**
* 根据回复id删除消息回复
* @parameter newsReviewId需删除的消息回复id
*/
public void removeNewsReview(Long id) {
getHibernateTemplate().delete(getNewsReview(id));
}
}
9.3.4 用Spring容器代替DAO工厂
通常情况下,引入接口就不可避免需要引入工厂来负责DAO组件的生成。但根据第5章的介绍可知,Spring实现了两种基本模式:单态模式和工厂模式。而使用Spring可以完全避免使用工厂模式,因为Spring就是个功能非常强大的工厂。因此,完全可以让Spring充当DAO工厂。
由Spring充当DAO工厂时,无须程序员自己实现工厂模式,只需要将DAO组件配置在Spring容器中,由ApplicationContext负责管理DAO组件的创建即可。借助于Spring提供的依赖注入,其他组件甚至不用访问工厂,一样可以直接使用DAO实例。
正如在工厂模式里必须要提供接口的实现类一样。此处的配置必须提供实现类,而不能仅提供接口,下面的配置代码是在applicationContext.xml里面配置了DAO组件。
<!-- 配置DAO组件,必须提供DAO的实现类-->
<bean id="dao" class="org.yeeku.dao.hibernate.BaseDAOHibernate">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置DAO组件,必须提供DAO的实现类-->
<bean id="newsDAO" class="org.yeeku.dao.hibernate.NewsDAOHibernate">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
在上面配置的property子元素里,引用了Hibernate的SessionFactory。其中,SessionFactory负责产生Hibernate Session。Hibernate的持久化操作必须在Session管理下完成。NewsDAOHibernate实现类并没有提供 setSessionFactory方法,该方法由其父类HibernateDaoSupport提供,用于为DAO组件依赖注入SessionFactory。
依此类推,编写其他几个PO的DAO接口和Hibernate实现,将它们配置在Spring的容器中。
通常建议将DAO组件以单独的配置文件配置,下面是daoContext.xml文件的源代码,该文件负责配置所有的DAO组件,并使用了继承简化DAO组件的配置:
<?xml version="1.0" encoding="GBK"?>
<!-- Spring配置文件的文件头,包含DTD等信息-->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- DAO模板,用于被其他的DAO组件继承 -->
<bean id="daoTemplate" abstract="true" lazy-init="true">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- Generic DAO - can be used when doing standard CRUD -->
<bean id="dao" class="org.yeeku.dao.hibernate.BaseDAOHibernate"
parent="daoTemplate"/>
<!-- 配置普通的DAO组件:UserDAO: -->
<bean id="userDAO" class="org.yeeku.dao.hibernate.UserDAOHibernate"
parent="daoTemplate"/>
<!-- 配置普通的DAO组件:NewsDAO -->
<bean id="newsDAO" class="org.yeeku.dao.hibernate.NewsDAOHibernate"
parent="daoTemplate"/>
<!-- 配置普通的DAO组件:NewsReviewDAO-->
<bean id="newsReviewDAO" class="org.yeeku.dao.hibernate.
NewsReviewDAOHibernate"
parent="daoTemplate"/>
<!-- 配置普通的DAO组件:CategoryDAO -->
<bean id="categoryDAO" class="org.yeeku.dao.hibernate.CategoryDAOHibernate"
parent="daoTemplate"/>
</beans>
至此,我们已经完成了Spring与Hibernate的整合。






