`
javafenger
  • 浏览: 243000 次
  • 来自: ...
文章分类
社区版块
存档分类
最新评论

不要重复编写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 实例负责一个主要域对象或实体。如果域对象具有独立生命周期,它应具有自己的 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 的无价帮助,他审阅了本文。

 

分享到:
评论

相关推荐

    泛型dao 泛型dao 泛型dao

    对于大多数开发人员,系统中的每个 DAO 编写几乎相同的代码到目前为止已经成为一种习惯。虽然所有人都将这种重复标识为 “代码味道”,但我们大多数都已经学会忍受它。能不能不写重复的dao 呢 ? 泛型dao,...

    Struts2+hibernate+spring整合泛型DAO

    减少重复代码的编写,增强DAO层代码的重用。

    虚拟数据层 Struts2、Hibernate、Spring整合的泛型DAO Version 2010.9.27

    对于大多数开发人员,系统中的每个 DAO 编写几乎相同的代码到目前为止已经成为一种习惯。虽然所有人都将这种重复标识为 “代码味道”,但我们大多数都已经学会忍受它。能不能不写重复的dao 呢 ? 泛型dao,...

    一个非常简单的MyBatis辅助工具,可以基于DAO的命名约定帮你生成并维护SQL语句

    在实际生产中减少了80%以上的重复SQL编写工作,从而把关注力转移到模型本身的制定上。结合建表语句生成插件pngen,大部分场景只需编写一个模型类即可完成DAO层工作。 支持最主流的MyBatis框架,无学习成本 基于常见...

    Spring整合hibernate(4)之BaseDao的编写示例

    说明:因为我们在实际编码中会编写一些重复的数据库操作方法CRUD,每个类都写一次感觉很繁琐,因此可以考虑考虑把所有公共的方法都写在BaseDao中,这个时候,让所有的DAO都继承BaseDao;这样基本上就实现了大量的...

    ssh代码自动生成器

    用ssh架构编写Web项目时,使用myEclipse的代码自动生成功能,很多时候不能满足我们的需要,很多时候需要手工写dao层,manager层,web层与及页面的代码,工作重复繁琐。 借助本工具结合myEclipse,不用写任何代码,就...

    Java CURD搭建MyBatis开发环境.docx

    减轻使用 JDBC 的复杂性,不用编写重复的创建 Connetion , Statement ; 不用编写关闭资源代码。 直接使用 java 对象,表示结果数据。让开发者专注 SQL 的处理。 其他分心的工作由 MyBatis 代劳 总的来说,mybatis...

    Java异常介绍及Spring Boot统一异常处理

    通过使用 @ControllerAdvice 和 @ExceptionHandler,可以将异常处理逻辑集中到一个地方,避免了在每个控制器中重复编写异常处理代码。同时,这种机制也使得异常处理更加灵活和可维护,可以根据需要自定义异常处理器...

    MVC+Extjs架构WebMis自动生成

    大家知道CRUD,查询分页都是重复的代码,更头疼的是Extjs文件的编写更愁人,而这些系统都可以根据你配置的表自动产生出来,不用再重复无用功了。 系统可以产生IDAO、DAO、Model、Nhibernate映射文件、IBLL、BLL、...

    根据数据表自动生成基础CRUD代码

    通过自动化这一过程,开发者可以节省大量时间,并专注于实现业务逻辑而不是编写重复的基础代码。 适用人群: 该资源适用于所有使用Java进行开发的团队和个人开发者。它特别适合那些需要快速搭建CRUD操作的应用,...

    Java_JDBC由浅入深

    8.6.3 重复读 59 8.6.4 序列化读 60 8.7 小结 62 第九节 PreparedStatement接口的使用 62 第十节 CallableStatement接口的使用 62 9.1 无参无返回值存储过程调用 63 9.2 有参无返回值存储过程调用 63 9.3 有参有...

    util-mybatis-generator.zip

    MyBatis代码生成器用来生成,Spring Boot整合MyBatisDao层代码减少代码开发量,帮助大家提升代码开发效率,有效的解决重复代码多次编写问题

    Spring-generator一键生成数据库文件

    Spring-generator 不是框架,它不会影响现有的任何机构,它只是一个解决你重复做某些事情的工具,它也不拘束与某个语言,它的使命就是将数据库表结构取出来,剩下的就取决于你怎么使用 FreeMarker 编写模板生成你想...

    基于JSP电子书城系统

    后台使用c3p0Pool及c3p0.properties配置连接数据库,使用java原生jdbc连接方式与数据库进行数据交互,控制层使用servlet作为与前端页面进行数据交互的基层,dao层编写jdbc对数据库进行操作的java代码,service层提供...

    rocket-api:API敏捷开发框架,用于API接口功能的快速开发。不再定义Controller,Service,Dao,Mybatis,xml,Entity,VO等对象和方法。以springboot starter形式集成使用

    告别加班,拒绝重复劳动,远离搬砖 概述 “ Rocket-API”基于spring boot的API敏捷开发框架,服务端50%以上的功能只需要写SQL或mongodb原始执行脚本即可完成开发,另外30%同时不停的完善公共组件,此类文件上传,...

    Excel_VBA教程

    5.使用DAO链接到ACCESS数据库 334 6.使用ADO链接到ACCESS数据库 334 7.从EXCEL执行ACCESS任务 336 8.创建新ACCESS数据库 336 9.打开ACCESS窗体 338 10.打开ACCESS报表 343 11.运行ACCESS查询 345 12.运行选择查询 ...

    Java面试宝典2010版

    13、在DAO中如何体现DAO设计模式? 14、spring+Hibernate中委托方案怎么配置? 15、spring+Hibernate中委托方案怎么配置? 16. hibernate进行多表查询每个表中各取几个字段,也就是说查询出来的结果集没有一个实体类...

    java web ssh框架 后台系统自动生成工具

    本工程是基于ssh框架的后台自动生成工具,该工具可以生成dao,daoImpl,service,serviceImpl,Action,applicationContext.xml,struts.xml,web.xml文件,可以使开发人员尽量少的进行编写重复代码以及避免一些错误...

    springboot旅游网站

    项目主要功能模块包括经典信息管理模块、用户信息管理模块、省份信息管理模块等,项目接口遵循restful风格,代码结构分布清晰,严格按照dao层-service层-controller层的设计规范编写代码,定义了统一的接口返回格式...

Global site tag (gtag.js) - Google Analytics