`
hain
  • 浏览: 454582 次
  • 来自: ...
社区版块
存档分类
最新评论

不要重复DAO!使用Hibernate 和Spring AOP 构建泛型类型安全的DAO

阅读更多
由于 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 的基础是其 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 映射,该域类具有两个属性:nameweightPerson 是具有上述属性的简单 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 的无价帮助,他审阅了本文。


下载

描述名字大小下载方法
Full source codej-genericdao.zip12KBHTTP
关于下载方法的信息Get Adobe® Reader®

参考资料

学习

获得产品和技术
  • Hibernate:Java 平台的持久性和查询服务。

  • Spring 框架:包括 Spring AOP 和 introductions 工具。


讨论

关于作者

Per Mellqvist 是瑞典斯德哥尔摩的一名系统架构师。他喜欢企业 Java 的所有方面,且欣赏能够很好地完成单独一件事的任何框架或工具。

评论
1 楼 Ozone 2009-03-14  
朋友,无意中找到了你这篇文章,学习了,不过最好把排版整理一下,满世界的下划线,看起来很不舒服

相关推荐

    Struts2+hibernate+spring整合泛型DAO

    Struts2、Hibernate和Spring是Java Web开发中的三大框架,它们各司其职,共同构建了一个强大的企业级应用体系。Struts2作为MVC(Model-View-Controller)框架,负责处理请求和展现视图;Hibernate是对象关系映射...

    再发一个基于Hibernate+Spring 泛型GenericDao 的工程源码.

    标题中的“基于Hibernate+Spring 泛型GenericDao”的工程源码指的是一个整合了Spring框架和Hibernate ORM技术的项目,其中使用了泛型设计模式来实现数据访问对象(DAO)。这个项目可能旨在提供一种通用、可复用且...

    Struts2 Spring3 Hibernate 注解功能 DAO 泛型 通用分页

    在"SSHWithAnnotationDemo"项目中,DAO层可能采用了泛型设计,泛型可以提高代码的复用性和安全性。例如,创建一个通用的DAO接口,使用泛型参数表示不同类型的数据实体,如`GenericDao&lt;T&gt;`,然后为每个具体的实体类...

    SpringAOP整合Hibernate并使用事务(模拟买书的过程)

    通过以上步骤,我们可以成功地整合Spring AOP和Hibernate,利用事务管理实现一个安全、可靠的数据操作流程。这个示例展示了AOP和ORM结合在实际项目中的强大功能,以及它们如何简化和优化企业级应用的开发。

    SpringMVC+springAOP+spring security+Hibernate整合实例代码

    Spring MVC、Spring AOP、Spring Security和Hibernate是Java开发中常用的四大框架,它们各自负责不同的领域,但在实际项目中往往需要进行整合以实现更高效、更安全的开发。本实例代码提供了一个完整的整合示例,涵盖...

    外文翻译计算机外文翻译Don't repeat the DAO!

    本文提出的基于Java 5泛型的类型安全DAO实现方案,有效地解决了传统DAO模式中存在的代码重复问题,并通过Spring AOP增强了其功能性。这种新方法不仅简化了开发流程,还提高了代码的质量和维护性,是现代企业级应用...

    hibernate和spring整合Java项目

    在Java开发领域,Hibernate和...总之,Hibernate与Spring的整合是Java开发中常见的实践,它结合了ORM的便捷和DI的灵活性,是构建现代Java应用的重要工具。理解并掌握这种整合方式,对于提升Java开发能力具有重要意义。

    Struts2,Spring与Hibernate整合应用,学生成绩管理系统

    - **AOP(面向切面编程)**:Spring AOP允许开发者定义“切面”来封装那些跨多模块的功能,如日志记录、安全控制等,从而提高代码的复用性和可维护性。 - **事务管理**:Spring提供了一套强大的事务管理机制,能够...

    Struts2+Hibernate+Spring整合开发深入剖析与范例应用03

    Struts2、Hibernate和Spring是Java企业级应用中三大核心框架,它们的整合使用能够构建出高效、可维护性高的Web应用程序。本篇将深入剖析这三者如何协同工作,并提供实际范例应用。 首先,Struts2作为MVC(模型-视图...

    hibernate与spring整合demo

    在Java开发领域,Hibernate和Spring都是极为重要的框架。Hibernate是一个对象关系映射(ORM)工具,它简化了数据库操作,而Spring则是一个全面的企业级应用框架,提供了依赖注入、AOP(面向切面编程)、MVC(模型-...

    用 hibernate 和 spring 开发持久层.doc

    【标题】: 使用Hibernate和Spring开发持久层 【描述】: 本文主要讲解如何利用Hibernate和Spring框架构建企业级应用的持久层,帮助开发者从初学者到专家的进阶。 【标签】: Hibernate 【正文】: 在Java开发中,...

    DAO模式设计

    使用AOP(Aspect Oriented Programming)可以在运行时检查泛型类型,确保DAO方法操作的是预期的实体类型,从而提供类型安全的泛型DAO。Spring AOP可以创建代理来拦截DAO方法调用,检查传入的参数类型是否匹配DAO的...

    spring_aop.rar_spring-dao

    本资源“spring_aop.rar_spring-dao”是一个关于Spring AOP(面向切面编程)和Spring DAO(数据访问对象)的实例,旨在帮助开发者理解如何在Spring框架下实现AOP的控制反转,并有效地进行DAO层的操作。 首先,我们...

    使用Struts + Spring + Hibernate完成用户登陆笔记

    * 使用Spring框架来实现依赖注入和AOP,例如使用Spring的HibernateDaoSupport来实现DAO层的开发。 六、系统优点 * 使用Struts、Spring和Hibernate三种技术可以实现一个灵活、可维护和高效的用户登陆系统。 * 系统...

    Spring2.5和Hibernate3集成--学习spring aop ioc

    Spring2.5和Hibernate3集成 采用声明式事务 1.声明式事务的配置 * 配置sessionFactory * 配置事务管理器 * 配置事务的传播特性 * 配置哪些类哪些方法使用事务 2.编写业务逻辑方法 * 继承...

    struts2+spring2.5+hibernate3基础包(包括AOP)

    综上所述,这个基础包提供了开发SSH整合项目的基础环境,开发者可以根据具体的业务需求,编写Action、Service、DAO层的代码,并利用Spring的AOP进行事务管理,从而构建出高效的企业级应用。同时,由于SSH的广泛使用...

    Spring与Hibernate集成

    在Java企业级应用开发中,Spring和Hibernate是两个非常重要的框架。Spring是一个全方位的轻量级应用框架,提供了强大的依赖注入、AOP(面向切面编程)以及各种服务管理功能。而Hibernate则是一个优秀的对象关系映射...

    springmvc+spring+hibernate

    Spring MVC、Spring 和 Hibernate 是Java Web开发中的三大主流框架,它们各司其职,共同构建了一个强大而灵活的后端架构。Spring MVC 负责处理HTTP请求并将其路由到相应的控制器,Spring 提供了依赖注入(DI)和面向...

    hibernate+spring复习大纲

    1. **Spring管理Hibernate Session**: 使用`HibernateTemplate`或`SessionFactoryBean`,在Spring容器中管理`SessionFactory`和`Session`。 2. **事务管理集成**: Spring的`PlatformTransactionManager`负责...

    10.6struts2+hibernate+spring的整合源代码

    Struts2、Hibernate和Spring是Java企业级应用中常用的三个开源框架,它们分别负责MVC模式中的表现层、持久层和业务层管理。SSH(Struts2+Spring+Hibernate)框架整合是Java Web开发中常见的一种技术栈,能有效地提高...

Global site tag (gtag.js) - Google Analytics