`

不要重复DAO

阅读更多

 

http://www.ibm.com/developerworks/cn/java/j-genericdao.html

 

原文链接

 

 

不要重复 DAO!

使用 Hibernate 和 Spring AOP 构建泛型类型安全的 DAO

 

级别: 中级

Per Mellqvist (per@mellqvist.name), 系统架构师, 自由作家

2006 年 6 月 05 日

由于 Java™ 5 泛型的采用,有关泛型类型安全 Data Access Object (DAO) 实现的想法变得切实可行。在本文中,系统架构师 Per Mellqvist 展示了基于 Hibernate 的泛型 DAO 实现类。然后展示如何使用 Spring AOP introductions 将类型安全接口添加到类中以便于查询执行。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

对于大多数开发人员,为系统中的每个 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 映射,该域类具有两个属性: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 code
j-genericdao.zip 12KB HTTP
 
分享到:
评论
2 楼 AKka 2014-04-16  

好强大!!!
1 楼 tanyongbing 2010-07-07  
vbc[color=red][/color]

相关推荐

    不要重复 DAO !

    标题“不要重复 DAO!”指的是在软件开发中,尤其是在Java领域,如何避免数据访问对象(DAO)层的代码重复,以提高代码复用性和维护性。DAO模式是将业务逻辑与数据库操作分离的一种设计模式,它使得应用代码可以独立...

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

    ### 外文翻译计算机外文翻译“不要重复DAO!”——基于Java 5泛型的类型安全数据访问对象实现 #### 概述 在计算机领域的软件开发过程中,数据访问层(Data Access Layer)的设计与实现至关重要。传统的数据访问...

    泛型dao 泛型dao 泛型dao

    能不能不写重复的dao 呢 ? 泛型dao,顾名思义就是一个dao可以对多个实体对象进行持久化。当应用中需要使用到上十张表时,DAO的维护变得日益困难,主要表现在这几个方面: 1)dao类的繁多,很多设计都是一个...

    DAO开发模式介绍.......

    1. **泛型DAO**:通过泛型,可以创建通用的DAO接口和实现,减少代码的重复。例如,`IMessageDAO` 和 `IUserDAO` 接口定义了基本的CRUD操作,而`HibernateDAO`基础类提供了对这些操作的实现,子类可以进一步扩展以...

    S2dao架包集合

    - **模板方法设计模式**:提供模板类,如`BeanHandler`和`MapHandler`,用于处理结果集,减少重复代码。 - **强大的SQL生成器**:支持动态SQL,可以根据条件自动生成合适的SQL语句。 - **事务管理**:提供事务控制...

    泛型通用DAO,可以很简化DAO层的代码

    这样,我们只需要为每种数据实体创建一个具体的DAO实现类,而无需为每个基本操作重复编写模板代码。 例如,我们可以有如下的泛型DAO接口: ```java public interface GenericDao&lt;T&gt; { void save(T entity); T ...

    C#特性标签实现通用Dao层

    总结起来,通过C#的特性标签和反射技术,我们可以构建一个灵活且可扩展的通用Dao层,大大减少了重复代码,提高了开发效率。这种方法的关键在于利用元数据(特性)来描述实体类和数据库之间的关系,以及利用反射在...

    hibenate 对DAO的封装

    本主题主要探讨的是如何使用泛型和反射技术来实现Hibernate对DAO的封装,从而创建一个通用的DAO模板,减少开发者重复编写CRUD(Create、Read、Update、Delete)操作的工作。 首先,我们需要理解DAO(Data Access ...

    Don’t repeat the DAO!

    ”指的是在软件开发中避免重复创建数据访问对象(DAO)模式的实践。DAO模式是设计模式的一种,它提供了一种在应用程序代码与数据库交互时解耦的方法。这篇博客可能探讨了如何有效地利用DAO,避免代码重复,提高可...

    简单DAO层示例

    这样可以避免代码重复,提高代码的复用性。例如,`AbstractDao`可以包含`executeQuery()`和`executeUpdate()`等通用方法,而`UserDaoImpl`只需实现具体的查询和更新逻辑。 在给定的`TestDao`中,我们可以猜测这是一...

    Hibernate 原生通用DAO

    在传统的Hibernate使用中,我们需要为每个实体类编写大量的CRUD(Create, Read, Update, Delete)方法,而使用原生通用DAO可以减少这部分重复工作,提高开发效率。 在设计原生通用DAO时,通常会包含以下核心功能: ...

    通用DAO

    通用DAO的概念旨在减少数据库操作的重复性工作,提高代码的可复用性和可维护性。本文将深入探讨Java语言中如何使用反射机制实现通用DAO,并通过提供的"通用DAO源码及示例"来阐述这一概念。 首先,我们需要理解什么...

    运用DAO和对象化进行重构_项目教程(2)_复习

    在本教程中,可能会涉及到提取方法、移动字段、替换条件为策略等重构技术,以消除重复代码、改善类和方法的设计,以及增强代码的表达力。 4. .NET框架:教程可能基于.NET框架进行,这是一个由微软开发的全面的开发...

    java的DAO开发

    对于重复的数据库操作,如增删改查,可以使用模板方法模式来抽象出公共部分,子类只需实现特定的部分。 10. **ORM框架** 虽然JDBC可以满足基本的数据库操作需求,但在大型项目中,通常会选择使用ORM(Object-...

    dao.rar_dao

    使用这样的设计,开发者可以快速地为新的实体类型创建DAO,减少了重复的代码编写,并且由于DAO层的解耦,可以更方便地更换底层的持久化技术,比如从Hibernate切换到MyBatis。 总结来说,"dao.rar_dao"提供的是一套...

    JDBC_DAO.rar

    4. **代码复用**:DAO可以被多个业务类共享,减少了代码重复。 在"JDBC_DAO.rar"中,可能包含的文件可能有以下几种: 1. **接口类**:定义了DAO接口,比如`UserDao.java`,其中声明了对用户表进行操作的方法,如`...

    MyEclipse自定义模板生成dao

    通过自定义模板,开发者可以设置模板变量,这些变量将在生成代码时被解析并替换为具体的实现内容,从而节省手动编写重复代码的时间。 **生成DAO的流程** 1. **配置模板**:首先,打开MyEclipse的首选项设置...

    ormlite使用中的dao简化写法

    在传统的ORMLite DAO实现中,我们需要为每个数据库表创建一个对应的DAO类,并在其中定义各种CRUD(Create、Read、Update、Delete)方法,这往往会导致大量重复且繁琐的代码。 标题"ormlite使用中的dao简化写法"指的...

    dao层自动生成

    "DAO层自动生成"这一技术旨在提高开发效率,减少手动编写重复代码的工作量。通过根据数据库表结构自动生成相关的实体类、DAO接口以及映射文件,开发者可以更专注于业务逻辑的实现,而不是基础的数据操作代码。 首先...

    java 基于泛型与反射的通用 DAO

    在Java编程语言中,泛型和反射是两个非常重要的...这种设计模式在实际开发中非常常见,尤其是在企业级应用中,能够有效地降低代码重复,提高开发效率。同时,通过接口定义,使得不同DAO之间的交互变得更加简单和规范。

Global site tag (gtag.js) - Google Analytics