由于 Java™ 5 泛型的采用,有关泛型类型安全 Data Access Object (DAO) 实现的想法变得切实可行。在本文中,系统架构师 Per Mellqvist 展示了基于 Hibernate 的泛型 DAO 实现类。然后展示如何使用 Spring AOP introductions 将类型安全接口添加到类中以便于查询执行。
对 于大多数开发人员,为系统中的每个 DAO 编写几乎相同的代码到目前为止已经成为一种习惯。虽然所有人都将这种重复标识为 “代码味道”,但我们大多数都已经学会忍受它。其实有解决方案。可以使用许多 ORM 工具来避免代码重复。例如,使用 Hibernate,您可以简单地为所有的持久域对象直接使用会话操作。这种方法的缺点是损失了类型安全。
为什么您要为数据访问代码提供类 型安全接口?我会争辩说,当它与现代 IDE 工具一起使用时,会减少编程错误并提高生产率。首先,类型安全接口清楚地指明哪些域对象具有可用的持久存储。其次,它消除了易出错的类型强制转换的需要(这是一个在查询操作中比在 CRUD 中更常见的问题)。最后,它有效利用了今天大多数 IDE 具备的自动完成特性。使用自动完成是记住什么查询可用于特定域类的快捷方法。
在本文中,我将为您展示如何避免再三地重复 DAO 代码,而仍保留类型安全接口的优点。事实上,您需要为每个新 DAO 编写的只是 Hibernate 映射文件、无格式旧 Java 接口以及 Spring 配置文件中的 10 行。
DAO 实现
DAO 模式对任何企业 Java 开发人员来说都应该很熟悉。但是模式的实现各不相同,所以我们来澄清一下本文提供的 DAO 实现背后的假设:
系统中的所有数据库访问都通过 DAO 进行以实现封装。
每个 DAO 实例负责一个主要域对象或实体。如果域对象具有独立生命周期,它应具有自己的 DAO。
DAO 负责域对象的创建、读取(按主键)、更新和删除(creations, reads, updates, and deletions,CRUD)。
DAO 可允许基于除主键之外的标准进行查询。我将之称为查找器方法 或查找器。查找器的返回值通常是 DAO 负责的域对象集合。
DAO 不负责处理事务、会话或连接。这些不由 DAO 处理是为了实现灵活性。
泛型 DAO 接口
泛型 DAO 的基础是其 CRUD 操作。下面的接口定义泛型 DAO 的方法:
清单 1. 泛型 DAO 接口
public interface GenericDao <T, PK extends Serializable> {
/** Persist the newInstance object into database */
PK create(T newInstance);
/** Retrieve an object that was previously persisted to the database using
* the indicated id as primary key
*/
T read(PK id);
/** Save changes made to a persistent object. */
void update(T transientObject);
/** Remove an object from persistent storage in the database */
void delete(T persistentObject);}
实现接口
用 Hibernate 实现清单 1 中的接口十分简单,如清单 2 所示。它只需调用底层 Hibernate 方法和添加强制类型转换。Spring 负责会话和事务管理。(当然,我假设这些函数已做了适当的设置,但该主题在 Hibernate 和 Springt 手册中有详细介绍。)
清单 2. 第一个泛型 DAO 实现
public class GenericDaoHibernateImpl <T, PK extends Serializable> implements GenericDao<T, PK>, FinderExecutor {
private Class<T> type;
public GenericDaoHibernateImpl(Class<T> type) {
this.type = type;
}
public PK create(T o) {
return (PK) getSession().save(o);
}
public T read(PK id) {
return (T) getSession().get(type, id);
} public void update(T o) {
getSession().update(o);
} public void delete(T o) {
getSession().delete(o);
}
// Not showing implementations of getSession() and setSessionFactory()
}
Spring 配置
最后,在 Spring 配置中,我创建了 GenericDaoHibernateImpl 的一个实例。必须告诉 GenericDaoHibernateImpl 的构造函数 DAO 实例将负责哪个域类。只有这样,Hibernate 才能在运行时知道由 DAO 管理的对象类型。在清单 3 中,我将域类 Person 从示例应用程序传递给构造函数,并将先前配置的 Hibernate 会话工厂设置为已实例化的 DAO 的参数:
清单 3. 配置 DAO
<bean id="personDao" class="genericdao.impl.GenericDaoHibernateImpl">
<constructor-arg>
<value>genericdaotest.domain.Person</value>
</constructor-arg>
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property></bean>
可用的泛型 DAO
我还没有完成,但我所完成的确实已经可以使用了。在清单 4 中,可以看到原封不动使用该泛型 DAO 的示例:
清单 4. 使用 DAO
public void someMethodCreatingAPerson() {
... GenericDao dao = (GenericDao)
beanFactory.getBean("personDao");
// This should normally be injected
Person p = new Person("Per", 90);
dao.create(p);}
现在,我有一个能够进行类型安全 CRUD 操作的泛型 DAO。让子类 GenericDaoHibernateImpl 为每个域对象添加查询能力将非常合理。因为本文的目的在于展示如何不为每个查询编写显式的 Java 代码来实现查询,但是,我将使用其他两个工具将查询引入 DAO,也就是 Spring AOP 和 Hibernate 命名的查询。
Spring AOP introductions
可 以使用 Spring AOP 中的 introductions 将功能添加到现有对象,方法是将功能包装在代理中,定义应实现的接口,并将所有先前未支持的方法指派到单个处理程序。在我的 DAO 实现中,我使用 introductions 将许多查找器方法添加到现有泛型 DAO 类中。因为查找器方法是特定于每个域对象的,因此将其应用于泛型 DAO 的类型化接口。
Spring 配置如清单 5 所示:
清单 5. FinderIntroductionAdvisor 的 Spring 配置
<bean id="finderIntroductionAdvisor" class="genericdao.impl.FinderIntroductionAdvisor"/>
<bean id="abstractDaoTarget" class="genericdao.impl.GenericDaoHibernateImpl" abstract="true">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id="abstractDao" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
<property name="interceptorNames">
<list>
<value>finderIntroductionAdvisor</value>
</list>
</property>
</bean>
在清单 5 的配置文件中,我定义了三个 Spring bean。第一个 bean 是 FinderIntroductionAdvisor,它处理引入到 DAO 的所有方法,这些方法在 GenericDaoHibernateImpl 类中不可用。我稍后将详细介绍 Advisor bean。
第二个 bean 是 “抽象的”。在 Spring 中,这意味着该 bean 可在其他 bean 定义中被重用,但不被实例化。除了抽象特性之外,该 bean 定义只指出我想要 GenericDaoHibernateImpl 的实例以及该实例需要对 SessionFactory 的引用。注意,GenericDaoHibernateImpl 类仅定义一个构造函数,该构造函数接受域类作为其参数。因为该 bean 定义是抽象的,所以我将来可以无数次地重用该定义,并将构造函数参数设置为合适的域类。
最后,第三个也是最有趣的 bean 将 GenericDaoHibernateImpl 的 vanilla 实例包装在代理中,赋予其执行查找器方法的能力。该 bean 定义也是抽象的,不指定希望引入到 vanilla DAO 的接口。该接口对于每个具体的实例是不同的。注意,清单 5 显示的整个配置仅定义一次。
扩展 GenericDAO
当然,每个 DAO 的接口都基于 GenericDao 接口。我只需使该接口适应特定的域类并扩展该接口以包括查找器方法。在清单 6 中,可以看到为特定目的扩展的 GenericDao 接口示例:
清单 6. PersonDao 接口
public interface PersonDao extends GenericDao<Person, Long> {
List<Person> findByName(String name);
}
很明显,清单 6 中定义的方法旨在按名称查找 Person。必需的 Java 实现代码全部是泛型代码,在添加更多 DAO 时不需要任何更新。
配置 PersonDao
因 为 Spring 配置依赖于先前定义的 “抽象” bean,因此它变得相当简洁。我需要指出 DAO 负责哪个域类,并且需要告诉 Springs 该 DAO 应实现哪个接口(一些方法是直接使用,一些方法则是通过使用 introductions 来使用)。清单 7 展示了 PersonDAO 的 Spring 配置文件:
清单 7. PersonDao 的 Spring 配置
<bean id="personDao" parent="abstractDao">
<property name="proxyInterfaces">
<value>genericdaotest.dao.PersonDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>genericdaotest.domain.Person</value>
</constructor-arg>
</bean>
</property>
</bean>
在清单 8 中,可以看到使用了这个更新后的 DAO 版本:
清单 8. 使用类型安全接口
public void someMethodCreatingAPerson() {
... PersonDao dao = (PersonDao)
beanFactory.getBean("personDao");
// This should normally be injected
Person p = new Person("Per", 90);
dao.create(p);
List<Person> result = dao.findByName("Per");
// Runtime exception}
虽然清单 8 中的代码是使用类型安全 PersonDao 接口的正确方法,但 DAO 的实现并不完整。调用 findByName() 会导致运行时异常。问题在于我还没有实现调用 findByName() 所必需的查询。剩下要做的就是指定查询。为更正该问题,我使用了 Hibernate 命名查询。
Hibernate 命名查询
使 用 Hibernate,可以在 Hibernate 映射文件 (hbm.xml) 中定义 HQL 查询并为其命名。稍后可以通过简单地引用给定名称来在 Java 代码中使用该查询。该方法的优点之一是能够在部署时优化查询,而无需更改代码。您一会将会看到,另一个优点是无需编写任何新 Java 实现代码,就可以实现 “完整的” DAO。清单 9 是带有命名查询的映射文件的示例:
清单 9. 带有命名查询的映射文件
<hibernate-mapping package="genericdaotest.domain">
<class name="Person">
<id name="id">
<generator class="native"/>
</id>
<property name="name" />
<property name="weight" />
</class>
<query name="Person.findByName">
<![CDATA[select p from Person p where p.name = ? ]]>
</query>
</hibernate-mapping>
清单 9 定义了域类 Person 的 Hibernate 映射,该域类具有两个属性:name 和 weight。Person 是具有上述属性的简单 POJO。该文件还包含一个在数据库中查找 Person 所有实例的查询,其中 “name” 等于提供的参数。Hibernate 不为命名查询提供任何真正的名称空间功能。出于讨论目的,我为所有查询名称都加了域类的短(非限定)名称作为前缀。在现实世界中,使用包括包名称的完全类名可能是更好的主意。
逐步概述
您已经看到了为任何域对象创建和配置新 DAO 所必需的全部步骤。三个简单的步骤是:
1. 定义一个接口,它扩展 GenericDao 并包含所需的任何查找器方法。
2. 将每个查找器的命名查询添加到域对象的 hbm.xml 映射文件。
3. 为 DAO 添加 10 行 Spring 配置文件。
查看执行查找器方法的代码(只编写了一次!)来结束我的讨论。
可重用的 DAO 类
使用的 Spring advisor 和 interceptor 很简单,事实上它们的工作是向后引用 GenericDaoHibernateImplClass。方法名以 “find” 打头的所有调用都传递给 DAO 和单个方法 executeFinder()。
清单 10. FinderIntroductionAdvisor 的实现
public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {
public FinderIntroductionAdvisor() {
super(new FinderIntroductionInterceptor());
}
}
public class FinderIntroductionInterceptor implements IntroductionInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();
String methodName = methodInvocation.getMethod().getName();
if (methodName.startsWith("find")) {
Object[] arguments = methodInvocation.getArguments();
return genericDao.executeFinder(methodInvocation.getMethod(), arguments);
} else {
return methodInvocation.proceed();
}
}
public boolean implementsInterface(Class intf) {
return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);
}
}
executeFinder() 方法
清单 10 的实现中惟一缺少的是 executeFinder() 实现。该代码查看调用的类和方法的名称,并使用配置上的约定将其与 Hibernate 查询的名称相匹配。还可以使用 FinderNamingStrategy 来支持其他命名查询的方法。默认实现查找叫做 “ClassName.methodName” 的查询,其中 ClassName 是不带包的短名称。清单 11 完成了泛型类型安全 DAO 实现:
清单 11. executeFinder() 的实现
public List<T> executeFinder(Method method, final Object[] queryArgs) {
final String queryName = queryNameFromMethod(method);
final Query namedQuery = getSession().getNamedQuery(queryName);
String[] namedParameters = namedQuery.getNamedParameters();
for(int i = 0; i < queryArgs.length; i++) {
Object arg = queryArgs[i];
Type argType = namedQuery.setParameter(i, arg);
}
return (List<T>) namedQuery.list();
}
public String queryNameFromMethod(Method finderMethod) {
return type.getSimpleName() + "." + finderMethod.getName();
}
结束语
在 Java 5 之前,该语言不支持编写既类型安全又 泛型的代码,您必须只能选择其中之一。在本文中,您已经看到一个结合使用 Java 5 泛型与 Spring 和 Hibernate(以及 AOP)等工具来提高生产率的示例。泛型类型安全 DAO 类相当容易编写 —— 您只需要单个接口、一些命名查询和为 Spring 配置添加的 10 行代码 —— 而且可以极大地减少错误并节省时间。
几乎本文的所有代码都是可重用的。尽管您的 DAO 类可能包含此处没有实现的查询和操作类型(比如,批操作),但使用我所展示的技术,您至少应该能够实现其中的一部分。参阅 参考资料 了解其他泛型类型安全 DAO 类实现。
致谢
自 Java 语言中出现泛型以来,单个泛型类型安全 DAO 的概念已经成为主题。我曾在 JavaOne 2004 中与 Don Smith 简要讨论了泛型 DAO 的灵活性。本文使用的 DAO 实现类旨在作为示例实现,实际上还存在其他实现。例如,Christian Bauer 已经发布了带有 CRUD 操作和标准搜索的实现,Eric Burke 也在该领域做出了工作。我确信将会有更多的实现出现。我要额外感谢 Christian,他目睹了我编写泛型类型安全 DAO 的第一次尝试并提出改进建议。最后,我要感谢 Ramnivas Laddad 的无价帮助,他审阅了本文。
分享到:
相关推荐
### 泛型DAO层在SSH框架中的应用与详解 #### 引言 在现代软件开发中,特别是基于Java的企业级应用开发中,DAO(Data Access Object)层的设计扮演着至关重要的角色。它作为业务逻辑层与数据持久层之间的桥梁,承担...
#### 泛型DAO模式概述 在Java软件开发过程中,DAO(Data Access Object)层的设计对于提高代码的可重用性与维护性至关重要。本文将详细介绍一种基于Hibernate技术的泛型DAO模式实现方法,并通过具体的接口定义和...
#### 描述详解:“泛型工具类,用于dao的超类,获取实体类的类型。” 在描述中提及的“dao的超类”,即数据访问对象(Data Access Object,简称DAO)模式中的基类或抽象类。DAO模式是软件架构设计中常用的一种模式...
本文将详细探讨一款基于Spring、iBatis和Struts2框架的学校图书管理系统,它不仅包含了所有必要的jar包和数据库文件,还提供了泛型DAO接口定义,以供开发者参考和使用。 首先,Spring框架作为系统的核心,负责管理...
Java泛型是Java SE 5.0引入的一个重要特性,它允许在定义类、接口以及方法时使用类型参数,从而增强了代码的复用性、安全性和可读性。在数据库应用中,泛型的运用可以使得数据库操作更加灵活且类型安全。 在上述...
**Java DAO模式实现详解** DAO(Data Access Object)模式是一种常用的设计模式,它在软件开发中主要用于数据库操作的抽象和封装。DAO模式的核心思想是将数据访问层与业务逻辑层解耦,使得业务代码不直接与数据库...
**IBATISDAO库类操作详解** 在Java开发中,IBATIS是一个流行的数据访问接口框架,它提供了灵活的SQL映射机制,使得开发者能够直接使用SQL语句进行数据库操作,而无需编写复杂的JDBC代码。`IBATISDAO`库类是基于...
在Spring3和Hibernate4的整合中,泛型常用于创建泛型DAO,如`UserDao<User>`,这样在处理不同类型的实体时,可以重用相同的DAO基础代码,提高了代码的复用性和可维护性。 4. **注解的应用**:Spring3和Hibernate4都...
例如,在Spring的DAO模板类中,尽管底层的数据连接或会话资源是非线程安全的,但是通过使用ThreadLocal,可以为每个线程绑定独立的数据连接或会话,使得多个DAO可以复用同一个模板实例而不会发生资源冲突,极大地...
### MyBatis 通用Mapper详解 #### 一、概述 MyBatis通用Mapper是一个基于MyBatis框架的扩展工具,它极大地简化了针对单一表的操作,使得开发人员无需编写繁琐的XML映射文件和接口定义就能实现数据层的增删改查等...
**Object Pascal语言详解** Object Pascal是一种基于Pascal编程语言的面向对象的方言,它扩展了原始的Pascal语法,引入了类、接口、异常处理、泛型等面向对象编程的关键特性。这一语言版本最初由Borland公司的...
* Java 正则表达式 API 详解及其应用 * Oracle 基础管理数据库基本概 * 数据库的创建、修改、删除和查询 * 索引的创建和主从表的建立 * 数据控制授权和回收 * 事务控制和查询语句 * SQL 语句中的函数使用 第三阶段...
#### 四、DAO接口详解 - **BaseDao**:定义了通用的CRUD操作,包括 `findAll()`, `findById()`, `save()`, `deleteById()` 等。 - **泛型定义**:`, ID extends Serializable>`,其中 `T` 表示实体类类型,`ID` ...
**Spring 3.0新特性详解** Spring框架作为Java企业级应用开发的基石,自发布以来不断进化,其中Spring 3.0版本是其一个重要里程碑,引入了许多创新特性和改进,提升了开发效率和应用程序的可维护性。在这个版本中,...
**通用架构更新——Generic Architecture详解** 在软件开发领域,提高代码复用性和模块化是提升效率的关键。"Generic Architecture",即通用架构,是一种旨在减少重复工作,提高开发效率的设计模式。本文将深入探讨...
- **实现**:由于在抽象Dao类中已经规定了泛型,因此在返回结果时无需进行额外的类型转换,可以直接返回相应类型的对象列表或者单个对象。 #### 四、总结 通过以上设计思路和实现步骤,我们可以看到手写一个ORM框架...