`
reasonsoft
  • 浏览: 47640 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

泛型dao 详细剖析

    博客分类:
  • java
 
阅读更多
      由于 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>       



<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->



可用的泛型 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 所必需的全部步骤。三个简单的步骤是:

<!--[if !supportLists]-->1.      <!--[endif]-->定义一个接口,它扩展 GenericDao 并包含所需的任何查找器方法。

<!--[if !supportLists]-->2.      <!--[endif]-->将每个查找器的命名查询添加到域对象的 hbm.xml 映射文件。

<!--[if !supportLists]-->3.      <!--[endif]-->为 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层代码,非常好用简易

    通过上述分析,我们可以看出,合理设计和运用泛型DAO层,不仅可以显著提高代码的复用性和维护性,还能极大地简化复杂的数据库操作,使得开发人员能够更加专注于业务逻辑的实现。因此,对于那些追求高质量、高效率的...

    Java Web程序运用中泛型DAO的作用.pdf

    文章详细阐述了泛型DAO模式的设计方法,包括在实现方法中直接定义泛型,或者在类和接口上定义泛型。泛型DAO模式通过抽象化设计,增强了程序的复用性和简化了编程语言,同时在程序运行结束后进行类型检查,避免了运行...

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

    例如,我们可以有如下的泛型DAO接口: ```java public interface GenericDao&lt;T&gt; { void save(T entity); T get(Class&lt;T&gt; clazz, int id); void update(T entity); void delete(int id); } ``` 然后,对于特定...

    SSH泛型代码实例

    - `DAO(1).rar`可能包含使用泛型的DAO设计模式示例,展示了如何创建泛型DAO以处理不同类型的数据对象。 综上所述,SSH框架结合泛型能够提升Java应用的开发效率和代码质量。通过学习和理解这些示例,开发者可以更好...

    泛型工具类

    在泛型背景下,DAO类通常会被设计成泛型,以便支持对不同类型的实体进行数据操作。`GenericUtil`类中的方法可以帮助这些DAO类动态获取其实体类型,这在动态创建查询、反射调用方法或进行类型转换时尤为重要。 #### ...

    反射与泛型

    在"反射与泛型.pptx"这个文件中,可能包含了对这些概念的详细讲解、示例代码以及实际应用案例,建议进一步查阅以获取更全面的知识。记住,理论知识的掌握只是第一步,实践和应用才能真正提升编程技能。

    通用DAO

    1. **创建泛型DAO接口**:定义一个泛型接口,如`GenericDAO&lt;T&gt;`,其中T代表数据库实体类型。 2. **实现DAO接口**:在实现类中,使用反射获取实体类的字段信息,构造SQL语句。 3. **执行SQL操作**:使用JDBC或其他ORM...

    反射泛型完美版分页.rar

    下面我们将详细探讨反射、泛型以及它们如何应用于分页。 首先,让我们理解什么是反射。反射是Java的一个核心特性,它允许程序在运行时检查和修改自身的结构和行为。通过java.lang.reflect包中的类(如Class、Method...

    java DAO模式实现 附源码

    3. 示例代码或博客文章,如`开博——【转】Java中DAO的实现 - sucra的专栏 - CSDN博客.htm`,可能详细介绍了DAO模式的使用和实践。 4. `daoexamples-0.8-src.zip`可能是DAO模式实现的源码示例库,包含完整的项目结构...

    万能javaDAO

    下面将详细介绍这个万能DAO的设计思想、实现方式以及可能涉及到的关键技术。 1. **设计思想** - **单一职责原则**:万能DAO的核心思想是将数据库操作的功能集中在一个类或一组类中,每个DAO类负责处理一种特定的...

    Java泛型与数据库应用实例详解

    2. **实战分析** - **OpenDB** 和 **CloseDB** 方法:这两个方法分别用于打开和关闭数据库连接。它们使用了JDBC(Java Database Connectivity)API来建立与MySQL数据库的连接。 - **Save方法**:这是实现保存数据...

    一个通用的Hibernate DAO

    根据提供的部分代码内容,我们可以对其中的关键知识点进行详细解析: 1. **包声明与导入语句** - `package com.lusm.HibernateSessionFactory;`:指定该Java类所在的包名。 - 导入了`java.io.Serializable`、`...

    java开发程序员简历

    * 使用 Hibernate 实现持久化层,使用泛型 DAO 封装添/删/改/查/分页/统计这些操作 * 实现细粒度权限管理使用 Struts2 中提供的拦截器实现对 Action 方法的拦截 * 粗粒度权限管理使用 Filter 实现 * 使用 Java、...

    MyProject2

    《MyProject2:MVP与Dagger2.0在项目中的深度整合》 在软件开发领域,MVP(Model-View-...通过分析这个项目,我们可以更好地理解这两种技术在实际项目中的应用,以及如何利用它们来优化代码结构和提高开发效率。

    CreateCodeC#三层结构代码生成软件V1..0.1

    在代码生成软件中,反射常用于分析现有类型和接口,以自动生成相应的代码。 - 泛型:泛型是C#中的一个重要特性,它允许创建可以适用于多种数据类型的类、接口和方法,提供更强的类型安全性和性能。 综合这些标签,...

    JPA学习笔记-EJB-06JPA+Spring使用经验

    通过创建一个泛型基类`BaseDaoImpl`,继承自Spring的`JpaDaoSupport`,为后续的DAO子类提供了一种统一的实现模式。`@Transactional`注解的使用,确保了所有数据访问操作都在事务边界内执行,从而增强了代码的一致性...

    Java个人简历模板37.doc

    - 负责业务模块划分、数据库设计,实现货运管理模块中的合同管理、出货表和统计报表等功能,进行代码重构优化,如利用泛型提高DAO层的代码重用度。 - 使用Jbpm实现流程控制,Struts2进行数据校验和文件下载,利用...

    第3章 业务系统架子的构建【自研框架的起源】.7z

    Service层是业务逻辑的主要承载者,它负责处理来自Controller层的请求,调用DAO层进行数据操作。3-6部分详细介绍了如何设计和搭建Service层,包括接口定义、事务管理、异常处理等方面,以实现松耦合和高内聚的架构...

    在JDK 1.5中实现真正面向对象形式的SQL语句3 (含有源码,代码生成工具)

    通过分析数据库模式,它可以生成具有泛型和枚举特性的SQL构建器类,使得SQL语句的构造更加直观和安全。 4. **面向对象SQL库**:在JDK 1.5之后,出现了许多面向对象的SQL库,如MyBatis。这些库允许开发人员通过接口...

Global site tag (gtag.js) - Google Analytics