- 浏览: 454582 次
- 来自: ...
文章分类
最新评论
-
edwardjuice:
淺顯易懂謝謝
context-param和init-param区别 -
ivan:
shihengli2010 写道我说下我的情况,客户给的数据是 ...
jxl读取excel文件异常:Unable to recognize OLE stream -
shihengli2010:
我说下我的情况,客户给的数据是xlsx类型的,打开报错,改下后 ...
jxl读取excel文件异常:Unable to recognize OLE stream -
brucechen:
刚刚解决这个问题了。我有一个表Picture保持路径字段设置不 ...
Hibernate 疑难异常及处理 -
yjb8119010:
context-param和init-param区别
对于大多数开发人员,为系统中的每个 DAO 编写几乎相同的代码到目前为止已经成为一种习惯。虽然所有人都将这种重复标识为 “代码味道”,但我们大多数都已经学会忍受它。其实有解决方案。可以使用许多 orM 工具来避免代码重复。例如,使用 Hibernate,您可以简单地为所有的持久域对象直接使用会话操作。这种方法的缺点是损失了类型安全。
为什么您要为数据访问代码提供类型安全接口?我会争辩说,当它与现代 IDE 工具一起使用时,会减少编程错误并提高生产率。首先,类型安全接口清楚地指明哪些域对象具有可用的持久存储。其次,它消除了易出错的类型强制转换的需要(这是一个在查询操作中比在 CRUD 中更常见的问题)。最后,它有效利用了今天大多数 IDE 具备的自动完成特性。使用自动完成是记住什么查询可用于特定域类的快捷方法。
在本文中,我将为您展示如何避免再三地重复 DAO 代码,而仍保留类型安全接口的优点。事实上,您需要为每个新 DAO 编写的只是 Hibernate 映射文件、无格式旧 Java 接口以及 Spring 配置文件中的 10 行。
DAO 模式对任何企业 Java 开发人员来说都应该很熟悉。但是模式的实现各不相同,所以我们来澄清一下本文提供的 DAO 实现背后的假设:
- 系统中的所有数据库访问都通过 DAO 进行以实现封装。
- 每个 DAO 实例负责一个主要域对象或实体。如果域对象具有独立生命周期,它应具有自己的 DAO。
- DAO 负责域对象的创建、读取(按主键)、更新和删除(creations, reads, updates, and deletions,CRUD)。
- 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 配置中,我创建了 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> |
我还没有完成,但我所完成的确实已经可以使用了。在清单 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 将功能添加到现有对象,方法是将功能包装在代理中,定义应实现的接口,并将所有先前未支持的方法指派到单个处理程序。在我的 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 显示的整个配置仅定义一次。
当然,每个 DAO 的接口都基于 GenericDao 接口。我只需使该接口适应特定的域类并扩展该接口以包括查找器方法。在清单 6 中,可以看到为特定目的扩展的 GenericDao 接口示例:
清单 6. PersonDao 接口
public interface PersonDao extends GenericDao<Person, Long> { List<Person> findByName(String name); } |
很明显,清单 6 中定义的方法旨在按名称查找 Person。必需的 Java 实现代码全部是泛型代码,在添加更多 DAO 时不需要任何更新。
因为 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 映射文件 (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 所必需的全部步骤。三个简单的步骤是:
- 定义一个接口,它扩展 GenericDao 并包含所需的任何查找器方法。
- 将每个查找器的命名查询添加到域对象的 hbm.xml 映射文件。
- 为 DAO 添加 10 行 Spring 配置文件。
查看执行查找器方法的代码(只编写了一次!)来结束我的讨论。
使用的 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); } } |
清单 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 |
关于下载方法的信息 | Get Adobe® Reader® |
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- “A single DAO for less code and simplicity”(Michael Slattery 的 Weblog,2005 年 8 月):展示泛型 DAO 实现。
- “Generic DAO pattern with JDK 5”(Christian Bauer 的 Weblog,2005 年 9月):Hibernate in Action 的作者详细介绍了用泛型实现的 DAO 模式。
- “使用 SDAO 进行 J2EE 测试的分步方法”(Kyle Brown,developerWorks,2003 年 3 月):包含 DAO 模式的概述。
- “高级 DAO 编程”(Sean Sullivan,developerWorks,2003 年 10 月):DAO 模式实现的面向设计讨论。
- “介绍 JDK 5.0 中的泛型”(Brian Goetz,developerWorks,2004 年 12 月):泛型类型的入门指南。
- “Java 理论和实践: 了解泛型”(Brian Goetz,developerWorks,2005 年 1 月):了解泛型的缺陷。
- Java 技术区:数百篇有关 Java 编程每个方面的文章。
获得产品和技术
讨论
- developerWorks blogs:加入 developerWorks 社区。
Per Mellqvist 是瑞典斯德哥尔摩的一名系统架构师。他喜欢企业 Java 的所有方面,且欣赏能够很好地完成单独一件事的任何框架或工具。
发表评论
-
Hibernate Annotation 中配置EhCache缓存
2008-01-04 11:30 52941. 首先设置EhCache,建立配置文件ehcache.x ... -
EasyJWeb+JPA(Hibernate3.2)+Spring2构架缓存技术
2008-01-03 14:45 2135总体思路:使用Hibernate3.2的二级缓存,解决使用频率 ... -
另类查询 Hibernate HQL 深度历险
2008-01-03 14:40 2142进入HQL世界 一个ORM框架是建立在面向对象的基础上的。最 ... -
hibernate3缓存管理
2008-01-03 14:34 1939Cache简介: 缓存(Cache ) ... -
Hibernate+ehcache二级缓存技术
2008-01-03 14:13 16771、首先设置EhCache,建立配置文件ehcache.xml ... -
Hibernate二级缓存攻略
2008-01-03 14:11 1363很多人对二级缓存都不 ... -
关于Hibernate的缓存之Hibernate的一级缓存理解....
2008-01-03 11:45 1980一级缓存 Hibernate的一级缓存是由Session提供的 ... -
Hibernate 3 Annotations 进阶
2008-01-03 11:25 2964安装 Hibernate Annotations为 ... -
Hibernate的缓存机制介绍
2008-01-03 10:42 1079缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对 ...
相关推荐
Struts2、Hibernate和Spring是Java Web开发中的三大框架,它们各司其职,共同构建了一个强大的企业级应用体系。Struts2作为MVC(Model-View-Controller)框架,负责处理请求和展现视图;Hibernate是对象关系映射...
标题中的“基于Hibernate+Spring 泛型GenericDao”的工程源码指的是一个整合了Spring框架和Hibernate ORM技术的项目,其中使用了泛型设计模式来实现数据访问对象(DAO)。这个项目可能旨在提供一种通用、可复用且...
在"SSHWithAnnotationDemo"项目中,DAO层可能采用了泛型设计,泛型可以提高代码的复用性和安全性。例如,创建一个通用的DAO接口,使用泛型参数表示不同类型的数据实体,如`GenericDao<T>`,然后为每个具体的实体类...
通过以上步骤,我们可以成功地整合Spring AOP和Hibernate,利用事务管理实现一个安全、可靠的数据操作流程。这个示例展示了AOP和ORM结合在实际项目中的强大功能,以及它们如何简化和优化企业级应用的开发。
Spring MVC、Spring AOP、Spring Security和Hibernate是Java开发中常用的四大框架,它们各自负责不同的领域,但在实际项目中往往需要进行整合以实现更高效、更安全的开发。本实例代码提供了一个完整的整合示例,涵盖...
本文提出的基于Java 5泛型的类型安全DAO实现方案,有效地解决了传统DAO模式中存在的代码重复问题,并通过Spring AOP增强了其功能性。这种新方法不仅简化了开发流程,还提高了代码的质量和维护性,是现代企业级应用...
在Java开发领域,Hibernate和...总之,Hibernate与Spring的整合是Java开发中常见的实践,它结合了ORM的便捷和DI的灵活性,是构建现代Java应用的重要工具。理解并掌握这种整合方式,对于提升Java开发能力具有重要意义。
- **AOP(面向切面编程)**:Spring AOP允许开发者定义“切面”来封装那些跨多模块的功能,如日志记录、安全控制等,从而提高代码的复用性和可维护性。 - **事务管理**:Spring提供了一套强大的事务管理机制,能够...
Struts2、Hibernate和Spring是Java企业级应用中三大核心框架,它们的整合使用能够构建出高效、可维护性高的Web应用程序。本篇将深入剖析这三者如何协同工作,并提供实际范例应用。 首先,Struts2作为MVC(模型-视图...
在Java开发领域,Hibernate和Spring都是极为重要的框架。Hibernate是一个对象关系映射(ORM)工具,它简化了数据库操作,而Spring则是一个全面的企业级应用框架,提供了依赖注入、AOP(面向切面编程)、MVC(模型-...
【标题】: 使用Hibernate和Spring开发持久层 【描述】: 本文主要讲解如何利用Hibernate和Spring框架构建企业级应用的持久层,帮助开发者从初学者到专家的进阶。 【标签】: Hibernate 【正文】: 在Java开发中,...
使用AOP(Aspect Oriented Programming)可以在运行时检查泛型类型,确保DAO方法操作的是预期的实体类型,从而提供类型安全的泛型DAO。Spring AOP可以创建代理来拦截DAO方法调用,检查传入的参数类型是否匹配DAO的...
本资源“spring_aop.rar_spring-dao”是一个关于Spring AOP(面向切面编程)和Spring DAO(数据访问对象)的实例,旨在帮助开发者理解如何在Spring框架下实现AOP的控制反转,并有效地进行DAO层的操作。 首先,我们...
* 使用Spring框架来实现依赖注入和AOP,例如使用Spring的HibernateDaoSupport来实现DAO层的开发。 六、系统优点 * 使用Struts、Spring和Hibernate三种技术可以实现一个灵活、可维护和高效的用户登陆系统。 * 系统...
Spring2.5和Hibernate3集成 采用声明式事务 1.声明式事务的配置 * 配置sessionFactory * 配置事务管理器 * 配置事务的传播特性 * 配置哪些类哪些方法使用事务 2.编写业务逻辑方法 * 继承...
综上所述,这个基础包提供了开发SSH整合项目的基础环境,开发者可以根据具体的业务需求,编写Action、Service、DAO层的代码,并利用Spring的AOP进行事务管理,从而构建出高效的企业级应用。同时,由于SSH的广泛使用...
在Java企业级应用开发中,Spring和Hibernate是两个非常重要的框架。Spring是一个全方位的轻量级应用框架,提供了强大的依赖注入、AOP(面向切面编程)以及各种服务管理功能。而Hibernate则是一个优秀的对象关系映射...
Spring MVC、Spring 和 Hibernate 是Java Web开发中的三大主流框架,它们各司其职,共同构建了一个强大而灵活的后端架构。Spring MVC 负责处理HTTP请求并将其路由到相应的控制器,Spring 提供了依赖注入(DI)和面向...
1. **Spring管理Hibernate Session**: 使用`HibernateTemplate`或`SessionFactoryBean`,在Spring容器中管理`SessionFactory`和`Session`。 2. **事务管理集成**: Spring的`PlatformTransactionManager`负责...
Struts2、Hibernate和Spring是Java企业级应用中常用的三个开源框架,它们分别负责MVC模式中的表现层、持久层和业务层管理。SSH(Struts2+Spring+Hibernate)框架整合是Java Web开发中常见的一种技术栈,能有效地提高...