为了自己更好的借鉴范型dao的理念,我这里决定将网上的一篇个人觉得非常优秀的介绍范型DAO设计方面的文章引入(我先声明,我引入纯粹是为了借鉴)。
译者:Nicholas @ Nirvana Studio
原文地址:http://www-128.ibm.com/developerworks/java/library/j-genericdao.html
使用Hibernate和Spring AOP购建一个范型类型安全的DAO
2006年五月12日
在采用了Java 5的范型之后,要实现一个基于范型类型安全的数据访问对象(DAO)就变得切实可行了。在这篇文章里,系统架构师Per Mellqvist展示了一个基于Hibernate的范型DAO实现。然后将介绍如何使用Spring AOP的introduction为一个类增加一个类型安全的接口以便于执行查询。
对于大多数开发者来说,在系统中为每一个DAO编写几乎一样的代码已经成为了一种习惯。同时大家也都认可这种重复就是“代码的味道”,我们中的大多数已经习惯如此。当然也有另外的办法。你可以使用很多ORM工具来避免代码的重复编写。举个例子,用Hibernate,你可以简单的使用session操作直接控制你的持久化领域对象。这种方式的负面影响就是丢失了类型安全。
为什么你的数据访问代码需要一个类型安全的接口?我认为它减少了编程错误,提高了生产率,尤其是在使用现代高级IDE的时候。首先,一个类型安全的接口清晰的制定了哪些领域对象具有持久化功能。其次,它消除了类型转换带来的潜在问题。最后,它平衡了IDE的自动完成功能。使用自动完成功能是最快的方式来记住对于适当的领域类哪些查询是可用的。
在这篇文章里,我将展示给大家如何避免一次次地重复编写DAO代码,但同时还收益于类型安全的接口。事实上,所有内需要编写的是为新的DAO编写一个Hibernate映射文件,一个POJO的Java接口,并且10行Spring配置文件。
DAO实现
DAO模式对于任何Java开发人员来说都是耳熟能详的。这个模式的实现相当多,所以让我们仔细推敲一下我这篇文章里面对于DAO实现的一些假设:
- 所有系统中的数据库访问都是通过DAO来完成封装
- 每一个DAO实例对一个主要的领域对象或者实体负责。如果一个领域对象具有独立的生命周期,那么它需要具有自己的DAO。
- DAO具有CRUD操作
- DAO可以允许基于criteria方式的查询而不仅仅是通过主键查询。我将这些成为finder方法或者finders。这个finder的返回值通常是DAO所负责的领域对象的集合。
范型DAO接口
范型DAO的基础就是CRUD操作。下面的接口定义了范型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实现上面的接口是非常简单的。也就是调用一下Hibernate的方法和增加一些类型转换。Spring负责session和transaction管理。
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运行时知道这个对象的类型。下面的代码中,我将领域类Person传递给构造器并且将Hibernate的session工厂作为一个参数用来实例化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
我还没有全部完成,但我现在已经有了一个可供作的代码。下面的代码展示了范型DAO如何使用:
public void someMethodCreatingAPerson() {
...
GenericDao dao = (GenericDao)
beanFactory.getBean("personDao"); // This should normally be injected
Person p = new Person("Per", 90);
dao.create(p);
}
这时候,我有一个范型DAO有能力进行类型安全的CRUD操作。同时也有理由编写GenericDaoHibernateImpl的子类来为每个领域对象增加查询功能。但是这篇文章的主旨在于展示如何完成这项功能而不是为每个查询编写明确的代码,然而,我将会使用多个工具来介绍DAO的查询,这就是Spring AOP和Hibernate命名查询。
Spring AOP介绍
你可以使用Spring AOP提供的introduction功能将一个现存的对象包装到一个代理里面来增加新的功能,定义它需要实现的新接口,并且将之前所有不支持的方法委派到一个处理机。在我的DAO实现里面,我用introduction将一定数量的finder方法增加到现存的范型DAO类里面。因为finder方法针对特定的领域对象,所以它们被应用到表明接口的范型DAO中。
<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>
在上面的配置中,我定义了三个Spring bean,第一个bean,FinderIntroductionAdvisor,处理那些introduce到DAO中但是不属于GenericDaoHibernateImpl类的方法。一会我再介绍Advisor bean的详细情况。
第二个bean定义为“abstract”。在Spring中,这个bean可以被其他bean重用但是它自己不会被实例化。不同于抽象属性,bean的定义简单的指出了我需要一个GenericDaoHibernateImpl的实例同时需要一个SessionFactory的引用。注意GenericDaoHibernateImpl类只定义了一个构造器接受领域类作为参数。因为这个bean是抽象的,我可以无限次的重用并且设定合适的领域类。
最后,第三个,也是最有意思的是bean将GenericDaoHibernateImpl的实例包装进了一个代理,给予了它执行finder方法的能力。这个bean定义同样是抽象的并且没有指定任何接口。这个接口不同于任何具体的实例。
扩展通用DAO
每个DAO的接口,都是基于GenericDAO接口的。我需要将为特定的领域类适配接口并且将其扩展包含我的finder方法。
public interface PersonDao extends GenericDao<Person, Long> {
List<Person> findByName(String name);
}
上面的代码清晰的展示了通过用户名查找Person对象列表。所需的Java实现类不需要包含任何的更新操作,因为这些已经包含在了通用DAO里。
配置PersonDao
因为Spring配置依赖之前的那些抽象bean,所以它变得很紧凑。我需要指定DAO负责的领域类,并且我需要告诉Spring我这个DAO需要实现的接口。
<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>
你可以这样使用:
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
}
上面的代码是使用类型安全接口PersonDao的一种正确途径,但是DAO的实现并没有完成。当调用findByName()的时候导致了一个运行时异常。这个问题是我还没有findByName()。剩下的工作就是指定查询语句。要完成这个,我使用Hibernate命名查询。
Hibernate命名查询
使用Hibernate,你可以定义任何HQL查询在映射文件里,并且给它一个名字。你可以在之后的代码里面方便的通过名字引用这个查询。这么做的一个优点就是能够在部署的时候调节查询而不需要改变代码。正如你一会将看到的,另一个好处就是实现一个“完整”的DAO而不需要编写任何Java实现代码。
<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>
上面的代码定义了领域类Person的Hibernate映射文件,有两个属性:name和weight。Person是一个具有上面属性的简单的POJO。这个文件同时包含了一个查询,通过提供的name属性从数据库查找Person实例。Hibernate为命名查询提供了不真实的命名空间功能。为了便于讨论,我将所有的查询名字的前缀变成领域类的的名称。在现实场景中,使用完整的类名,包含包名,是一个更好的主意。
总览
你已经看到了为任何领域对象创建并配置DAO的所需步骤了。这三个简单的步骤就是:
- 定义一个接口继承GenericDao并且包含任何所需的finder方法
- 在映射文件中为每个领域类的finder方法增加一个命名查询。
- 为DAO增加10行Spring配置
可重用的DAO类
Spring advisor和interceptor的功能比较琐碎,事实上他们的工作都引用回了GenericDaoHibernateImpl类。所有带有“find”开头的方法都被传递给DAO的单一方法executeFinder()。
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() 方法
上面的代码唯一缺的就是executeFinder的实现。这个代码观察被调用的类的名字和方法,并且将他们与Hibernate的查询名相匹配。你可以使用一个FinderNamingStrategy来激活其他方式的命名查询。默认的实现查找一个名为“ClassName.methodName”的查询,ClassName是除包名之外的类名。
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语言并不支持代码同时具有类型安全和范性的特性;你不得不二者选一。在这篇文章里,你可以看到使用Java 5范型支持并且结合Spring和Hibernate(和AOP)一起来提高生产力。一个范型类型安全的DAO类非常容易编写,所有你需要做的就是一个接口,一些命名查询,并且10行Spring配置,并且可以极大的减少错误,同时节省时间。
分享到:
相关推荐
《C++多范型设计》是一本深入探讨C++编程语言中模板技术的专著,由知名软件工程师James O. Coplien撰写,并由鄢爱兰、周辉等翻译成中文版,ISBN号为9787508318240。这本书的核心主题是C++中的泛型编程,它是C++编程...
本文提出的基于消息传递范型和客户机/服务器(Client/Server,简称C/S)范型双范型的主数据管理机制,能够有效解决MDM面临的问题。 消息传递范型是计算机科学中的一个基本概念,用于描述进程间通信的方式。在这范型...
《范型程序设计与 STL》是一本深入探讨C++编程中的关键概念和技术的书籍,主要聚焦于范型(Generic Programming)和标准模板库(Standard Template Library,简称STL)。范型编程是一种强大的软件开发方法,它允许...
《分布式系统原理与范型》作为一本系统介绍分布式系统基本原理与实践应用的书籍,涵盖了分布式系统设计与实现的核心理念。分布式系统指的是由多个可以独立运行的计算单元构成的系统,这些计算单元通过通信网络相互...
Java 范型Java 范型
《分布式系统原理与范型》是分布式系统中的经典教材,全书分为两部分:原理和范型。第一部分详细讨论了分布式系统的原理、概念和技术,其中包括通信、进程、命名、同步、一致性和复制、容错以及安全。第二部分给出了...
书名:C++多范型设计 作者:[美]James O.Coplien 译者: 出版社:中国电力出版社 本书详细地介绍了从“应用领域”到“方案领域”的C++设计实现方法,以及开发者在设计思考和设计实践过程中需要用到的记法、图表和设计...
分布式系统原理与范型是IT领域中至关重要的一个主题,特别是在云计算、大数据处理以及现代互联网服务的背景下,理解和掌握分布式系统的概念与实践至关重要。本书作为第二版,深度探讨了这一领域的核心理论和常见模式...
分布式系统原理与范型是计算机科学中的一个重要领域,它涵盖了多台计算机如何通过网络协同工作,以实现一个共同的目标。《分布式系统原理与范型(第二版)》这本书深入浅出地介绍了这一主题,旨在帮助读者理解分布式...
第2~9章讨论的是分布式系统的的原理、概念和技术,包括通信、进程、命名、同步化、一致性和复制、容错性以及安全性等,而分布式应用的开发方法(即范型)在第10~13章中进行了讨论。但是,与前一版不同的是,我们...
C++多范型设计.pdfC++多范型设计.pdfC++多范型设计.pdf
分布式系统-原理与范型(第2版) 分布式系统-原理与范型(第2版) 分布式系统-原理与范型(第2版) 分布式系统-原理与范型(第2版)
### Java范型攻略篇:深度解析与应用 #### 一、引言:Java范型的引入 在Java的发展历程中,范型(Generics)的引入标志着语言设计上的一个重要里程碑。自Java 1.5发布以来,范型成为了Java语言的重要特性之一,极...
Tanenbaum教授是早期操作系统领域的知名学者,其著作深入浅出地介绍了分布式系统的基本原理和实现范型。 分布式系统的原理部分是该书的核心内容,涵盖了多个重要的概念和原理。首先,通信是分布式系统中最为基本的...
华中科技大学 分布式系统原理与范型_考试_2009_答案
分布式系统原理与范型是计算机科学中的一个重要领域,它涉及到多台计算机协同工作,共同处理一个任务或数据,以提供高可用性、可扩展性和性能优化。这些课件旨在为学习者提供一个全面且系统的分布式系统知识框架。...
分布式系统原理与范型中文版 经典分布式理论书籍
分布式系统原理与范型(下).pdf 清华大学出版社,不错的分布式教材。
分布式系统是一种由多台计算机组成的网络系统,这些计算机在用户看来就像是一个单一的系统。它们通过网络互相通信和协调工作,...通过理解分布式系统原理与范型,可以更好地设计和部署高效、可靠、可维护的分布式应用。