`
isiqi
  • 浏览: 16550041 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

DAO设计模式笔记

阅读更多

DAO设计模式

DAO(Data Access Object)模式实际上是两个模式的组合,即Data Accessor 模式和 Active Domain Object 模式,其中 Data Accessor 模式实现了数据访问和业务逻辑的分离,而Active Domain Object 模式,其中Data Accessor模式实现了数据访问和业务逻辑的分离,而Active Domain Object 模式实现了业务数据的对象化封装,一般我们将这两个模式组合使用,因此,考虑到这些因素,这里将其作为同一个主题加以讨论。如图展示了DAO模式的实现层次。

DAO模式通过对业务层提供数据抽象层接口,实现了以下目标:

1. 数据存储逻辑的分离
通过对数据访问逻辑进行抽象,为上层机构提供抽象化的数据访问接口。业务层无需关心具体的select,insert,update操作,这样,一方面避免了业务代码中混杂JDBC调用语句,使得业务落实实现更加清晰,另一方面,由于数据访问几口语数据访问实现分离,也使得开发人员的专业划分成为可能。某些精通数据库操作技术的开发人员可以根据接口提供数据库访问的最优化实现,而精通业务的开发人员则可以抛开数据曾德繁琐细节,专注于业务逻辑编码。

2. 数据访问底层实现的分离
DAO模式通过将数据访问计划分为抽象曾和实现曾,从而分离了数据使用和数据访问的地称实现细节。这意味着业务层与数据访问的底层细节无关,也就是说,我们可以在保持上层机构不变得情况下,通过切换底层实现来修改数据访问的具体机制,常见的一个例子就是,我们可以通过仅仅替换数据访问曾实现,将我们的系统部署在不同的数据库平台之上。

3. 资源管理和调度的分离
在数据库操作中,资源的管理和调度是一个非常值得关注的主题。大多数系统的性能瓶颈往往并非集中于业务逻辑处理本身。在系统涉及的各种资源调度过程中,往往存在着最大的性能黑洞,而数据库作为业务系统中最重要的系统资源,自然也成为关注的焦点。DAO模式将数据访问逻辑从业务逻辑中脱离开来,使得在数据访问层实现统一的资源调度成为可能,通过数据库连接池以及各种缓存机制(Statement Cache,Data Cache等,缓存的使用是高性能系统实现的一个关键所在)的配合使用,往往可以保持上层系统不变的情况下,大幅度提升系统性能。

4.数据抽象
在直接基于JDBC调用的代码中,程序员面对的数据往往是原始的RecordSet数据集,诚然这样的数据集可以提供足够的信息,但对于业务逻辑开发过程而言,如此琐碎和缺乏寓意的字段型数据实在令人厌倦。
DAO 模式通过对底层数据的封装,为业务曾提供一个面向对象的接口,使得业务逻辑开发员可以面向业务中的实体进行编码。通过引入DAO模式,业务逻辑更加清晰,且富于形象性和描述性,这将为日后的维护带来极大的便利。试想,在业务曾通过Customer.getName方法获得客户姓名,相对于直接通过SQL语句访问数据库表并从ResultSet中获得某个字符型字段而言,哪种方式更加易于业务逻辑的形象化和简洁化?

空洞地谈些理论固然没有什么价值,我们需要看到的是通过对应用设计模式之后,我们的代码到底有怎样的改观,进而才能对设计带来的优劣有所感悟。下面让我们来看看代码:

代码
  1. PublicBigDecimalcalcAmount(StringcustomerID,BigDecimalamount){
  2. //根据客户ID获得客户记录
  3. Customercustomer=CustomerDAO.getCustomer(customerID);
  4. //根据客户登记获得打折规则
  5. Promotionpromotion=PromotionDAO.getPromotion(customer.getLevel());
  6. //累积客户总消费额,并保存累计结果
  7. Customer.setSumAmount(customer.getSumAmount().add(amount));
  8. CustomerDAO.save(customer);
  9. //返回打折后金额
  10. Returnamount.multiply(promotion.getRatio());
  11. }
render_code(); 这样的代码相信已经足够明晰,即使对于缺乏数据库技术基础的读者也可以轻松阅读。

从上面这段代码中,我们可以看到,通过DAO模式对各个数据库对象进行封装,我们对业务层屏蔽了数据库访问的底层实现,业务曾仅包含与本领域相关的逻辑对象和算法,这样对于业务逻辑开发人员(以及日后专注于业务逻辑的代码阅读者)而言,面对的是一个简洁明快的逻辑实现结构。业务层的开发和维护将变得更加简单。
DAO模式中,数据库访问层实现被隐藏到Data Accessor中,前面说过,DAO模式实际上是两个模式的组合,即Data Accessor 和 Domain Object模式。
何谓 Data Accessor?即将数据访问的实现机制加以封装,与数据的使用代码相分离,从外部来看,Data Accessor 提供了黑盒式的数据存取接口。

Domain Object则提供了对所面向领域内对象的封装。
从某种意义上,我们可以这么理解:

代码
  1. DataAccessorobject(DAO)=Data+Accessor+domainobject
render_code();

这个等式自左向右,形象地描述了设计分离的3个层次。
现在,对于上面的例子,来看看业务层后所隐藏的实现细节:
首先,我们这个计算打折后金额的业务过程中,涉及了两个业务对象,即客户对象Customer,和促销规则对象Promotion。自然,这两个对象也就成为了此业务领域(Business Domain)中的Domain Object,所谓Domain Object,简单来讲就是对领域内(Domain)涉及的各个数据对象,反映到代码,就是一个拥有相关属性的getter,setter方法的JavaClass(Java Bean)

以Customer和CustomerDao为例,实现代码如下(Promotion 和 PromotionDAO的实现代码与此类似):

DAO 模式的进一步改良

上面的例子中我们通过DAO模式实现了业务路基与数据逻辑的分离。对于专项开发(为特定客户环境指定的特定业务系统)而言,这样的分离设计差不多已经可以实现开发过程中业务层面与数据层面的相对独立,并且在实现复杂性与结构清晰性上达到较好的平衡。

然而,对于一个产品化的业务系统而言,目前的设计却仍稍显不足。相对专项原发的软件项目而言,软件产品往往需要在不同客户环境下及时部署。一个典型情况就是常见的论坛系统,一个商业论坛系统可能会部署在厂前上万个不同的客户环境中。诚然,由于java良好的跨平台支持,我们在操作系统之间大可轻易迁移,但在另外一个层面,数据库层,却仍然面临着平台迁移的窘境。客户可能已经购买了Oracle,SQLServer,Sybase 或者其他类型的 数据库。这就意味着我们的产品必须能部署在这些平台上,才能满足客户的需求。

对于我们现有的设计而言,为了满足不同客户的需求,我们可以实现针对不同类型数据库的
Data Accessor,并根据客户实际部署环境,通过类文件的静态替换来实现。显然,这样的实现方式在面对大量客户和复杂的部署环境时,将大大增加部署和维护工作的难度和复杂性。回忆一下“开闭原则”(Open-Close Principle) –对扩展开放,对修改封闭。我们应该采取适当的设计,将此类因素带来的变动(类的静态替换)屏蔽在系统之外。

为了实现跨数据库平台移植,或者展开来说,为了支持不同数据访问机制之间的可配置切换,我们需要在目前的DAO层引入Factory模式和Proxy模式。

这里所谓的不同数据访问机制,包括了不同数据库本身的访问实现,同时也包括了对于同一数据库德不同访问机制的兼容。例如我们的系统部署在小客户环境中,可能采用了基于JDBC的实现,而在企业环境中部署时,可能采用CMP作为数据访问的底层实现,以获得服务器集群上的性能优势(CMP具体怎样还有待商榷,这里暂且将其作为一个理由)。

Factory模式的引入

由于需要针对不同的数据库访问机制分别提供各自版本的Data Accessor实现,自然我们会想通过 Java Interface 定义一个调用接口,然后对这个调用接口实现不同数据库的 Data Accessor。通过以接口作为调用界面和实现规范,我们就可以避免代码只能给对具体实现的依赖。
对于例子中的CustomerDAO而言,我们可以抽象出如下的接口:

代码
  1. PublicinterfaceCustomerDAO{
  2. PublicCustomergetCustomer(StringcustID);
  3. Puboicvoidsave(Customercustomer);
  4. }
render_code();

这里作为示例,提供了两个实现,一个基于MySql数据库,一个基于Oracle,对这里的简单示例而言,基于Oracle和MySql的实现并没有什么太大区别,只是为了说明系统设计的结构。

作为最常用的创建模式,Factory模式在这里起到来接接口和实现的桥梁作用。通过Factory模式,我们可以根据具体需要加载相应得实现,并将此实现作为所对应接口的一个实例提供给业务层使用:

代码
  1. CustomerDAOcustDAO=(CustomerDAO)DAOFactory.getDAO(CustomerDAO.class);
  2. Customercustomer=custDAO.getCustomer(customerID);
render_code();

通过上面的代码我们可以看到,通过接口我们将具体的DAO实现从代码中分离。
也就是说,业务层通过接口调用底层实现,具体的DAO实现类不会出现在我们的业务代码中。而具体实现类在配置文件中加以配置,之后DAOFactory.getDAO方法通过读取配置文件获得当前我们期望使用的视线类的类名,再通过Java Class动态加载机制加载后返回。

从而我们的代码并不依赖于某个特定的实现类,只需要在部署的时候在配置文件中指定当前采用的实现类即可。
本例中,为了提高性能,避免每次调用都读取配置文件所引起的大量磁盘操作,采用了HashMap作为DAO缓存实现示例:

代码
  1. packagenet.wanjin.lab.persistence.dao;
  2. importjava.util.HashMap;
  3. publicclassDAOFactory{
  4. privatestaticHashMapdaoMap=null;
  5. /**
  6. *ReturnaimplemetationinstanceofthespecifiedDAOInterface
  7. *@returntheDAOImplemmenationClassInstance
  8. */
  9. publicstaticObjectgetDAO(ClassdaoInterface){
  10. initial();
  11. Objectdao=daoMap.get(daoInterface);
  12. if(null==dao){
  13. thrownewDAOException("NoImplementationfoundofDAOinterface=>"
  14. +daoInterface.getName());
  15. }
  16. returndao;
  17. }
  18. /**
  19. *InitialtheDAOFactory
  20. *LoadDAOInterfaceandImplementationIndaoMapforlateruse
  21. */
  22. publicstaticsynchronizedvoidinitial(){
  23. if(null==daoMap){
  24. daoMap=DAOConfig.load();//根据配置文件加载DAO实现配置
  25. }
  26. }
  27. }
render_code();

代码
  1. packagenet.wanjin.lab.persistence.dao;
  2. importjava.util.Enumeration;
  3. importjava.util.HashMap;
  4. importjava.util.Properties;
  5. importorg.apache.log4j.LogManager;
  6. importorg.apache.log4j.Logger;
  7. /**
  8. *DAOConfig类实现了配置文件的读取功能,并根据配置文件中的内容加载制定的接口和实现类;
  9. *@authorAdministrator
  10. */
  11. publicclassDAOConfig{
  12. privatestaticLoggerlogger=LogManager.getLogger(DAOConfig.class);
  13. privatestaticfinalStringDAO_CONFIG_FILE="dao.xml";
  14. privatestaticfinalStringDAO_CONFIG_SECTION="DAO";
  15. /**
  16. *LoadtheDAOInterface_ImplementationintoaHashMap
  17. *@return
  18. */
  19. publicstaticsynchronizedHashMapload(){
  20. HashMapmap=newHashMap();
  21. JFigLocatorjfigLocator=newJFigLocator(DAO_CONFIG_FILE);
  22. JFigIFdaoConfig=JFig.getInstance(jfigLocator);
  23. Propertiesprop=daoConfig.getSectionAsProperties(DAO_CONFIG_SECTION);
  24. EnumerationenumSection=prop.keys();
  25. while(enumSection.hasMoreElements()){
  26. StringdaoIface=(String)enumSection.nextElement();
  27. StringdaoImpl=prop.getProperty(daoIface);
  28. try{
  29. Classiface=ClassToolKit.loadClass(daoIface);
  30. Classimpl=ClassToolKit.loadClass(daoImpl);
  31. //将接口作为HashMap索引,实现类作为值
  32. map.put(iface,impl);
  33. }catch(ClassNotFoundExceptione){
  34. logger.debug("NoClassFound"+e);
  35. }
  36. }//whileenumSection
  37. returnmap;
  38. }
  39. }[/code[code]]//dao.xml文件
  40. <?xmlversion="1.0"encoding="UTF-8"?>
  41. <configuration>
  42. <sectionname="DAO">
  43. <entrykey="net.wanjin.lab.persistence.dao.iface.CustomerDAO"
  44. value="net.wanjin.lab.persistence.dao.impl.CustomerDAOImp_Mysql"/>
  45. <entrykey="net.wanjin.lab.persistence.dao.iface.PromotionDAO"
  46. value="net.wanjin.lab.persistence.dao.impl.PromotionDAOImp_Mysql"/>
  47. </section>
  48. </configuration>
render_code();
DAOConfig中使用了JFig读取XML配置文件(dao.xml),关于JFig的具体信息请参见http://jfig.sourceforge.net.

代码
  1. packagenet.wanjin.lab.persistence.dao;
  2. publicclassClassToolKit{
  3. publicstaticClassloadClass(StringclassName)
  4. throwsClassNotFoundException{
  5. Classcls=null;
  6. try{
  7. //首先尝试用当前ClassLoader加载
  8. cls=Thread.currentThread().getContextClassLoader().loadClass(className);
  9. }catch(Exceptione){
  10. e.printStackTrace();
  11. }
  12. if(cls==null){
  13. //如果通过当前ClassLoader加载失败,使用系统ClassLoader加载
  14. cls=Class.forName(className);
  15. }
  16. returncls;
  17. }
  18. }
render_code(); 这样,通过接口与实现相分离,并结合DAOFactory动态加载实现类,我们实现了底层访问实现的参数化配置功能。从而为增强产品的部署能力提供了强有力的支持。

经过Factory模式的改造,我们业务层代码也进行了相应得修改:

代码
  1. packagenet.wanjin.lab.persistence.dao;
  2. importjava.math.BigDecimal;
  3. importnet.wanjin.lab.persistence.domain.Customer;
  4. publicclassCustomers{
  5. publicBigDecimalcalcAmount(StringcustomerID,BigDecimalamount){
  6. //根据客户ID获得客户记录
  7. CustomerDAOcustomerDAO=(CustomerDAO)DAOFactory.getDAO(CustomerDAO.class);
  8. Customercustomer=customerDAO.getCustomer(customerID);
  9. //根据客户等级获得打折比率
  10. PromotionDAOpromoDAO=(PromotionDAO)DAOFactory.getDAO(PromotionDAO.class);
  11. Promotionpromotion=promoDAO.getPromotion(customer.getLevel());
  12. //累计客户总消费,并更新数据库
  13. customer.setSumAmount(customer.getSumAmount().add(amount));
  14. customerDAO.save(customer);
  15. //返回打折后金额
  16. returnamount.multiply(promotion.getRatio());
  17. }
  18. }
render_code();
似乎出现了一些Bad Smell,相对于改造前的calcAmount方法,这段代码里混杂了一些数据访问层的内容,如DAOFactory.getDAO方法的调用。虽然有众多的理由解释引入DAOFactory.getDAO所带来的好处,但事实是,无论有多好的理由新的设计必须避免影响业务逻辑代码的可读性。没有哪家公司能说服你在自己的房屋中增加一条穿堂而过的管道,而理由是为了实施更好的供暖设计,我们软件也一样。

Proxy 模式的引入

为了保持业务代码的简洁,将Factory模式带来的Bad Smell排除在系统之外。我们引入了结构模式中的Proxy模式。

Proxy模式的作用事通过提供一个中间层(Proxy),将上层调用接口与下层实现相衔接,其标准实现如下。

代码
  1. packagenet.wanjin.lab.persistence.dao;
  2. importjava.math.BigDecimal;
  3. publicclassDecoupleByDesign{
  4. publicBigDecimalcalcAmount(StringcustomerID,BigDecimalamount){
  5. //根据客户ID获得客户记录
  6. Customercustomer=CustomerProxy.getCustomer(customerID);
  7. //根据客户等级获得打折比率
  8. Promotionpromotion=PromotionProxy.getPromotion(customer.getLevel());
  9. //累计客户消费额,并更新数据库
  10. customer.setSumAmount(customer.getSumAmount().add(amount));
  11. CusromerProxy.save(customer);
  12. //返回打折后金额
  13. returnamount.multiply(promotion.getRatio());
  14. }
  15. }
render_code();
Bad Smell消失了,业务层在次变得干净简洁。而CustomerProxy 和PromotionProxy做了些什么呢?其实很简单:
代码
  1. packagenet.wanjin.lab.persistence.dao;
  2. importjava.math.BigDecimal;
  3. importnet.wanjin.lab.persistence.domain.Customer;
  4. publicclassDecoupleByDesign{
  5. publicBigDecimalcalcAmount(StringcustomerID,BigDecimalamount){
  6. //根据客户ID获得客户记录
  7. Customercustomer=CustomerProxy.getCustomer(customerID);
  8. //根据客户等级获得打折比率
  9. Promotionpromotion=PromotionProxy.getPromotion(customer.getLevel());
  10. //累计客户消费额,并更新数据库
  11. customer.setSumAmount(customer.getSumAmount().add(amount));
  12. CustomerProxy.save(customer);
  13. //返回打折后金额
  14. returnamount.multiply(promotion.getRatio());
  15. }
  16. }
render_code();

代码
  1. packagenet.wanjin.lab.persistence.dao;
  2. publicclassPromotionProxy{
  3. /**
  4. *GetPromotionObjectbyPromotionLevel
  5. *@paramlevel
  6. *@return
  7. */
  8. publicstaticPromotiongetPromotion(intlevel){
  9. PromotionDAOpromoDAO=(PromotionDAO)DAOFactory.getDAO(PromotionDAO.class);
  10. returnpromoDAO.getPromotion(level);
  11. }
  12. }
render_code();
至此,通过Factory和Proxy模式的应用,我们对原有的DAO模式进行了改造,在不影响业务曾清晰性的前提下,提供了底层实现的参数配置化实现。
最后,让我们通过下面这个Sequence Diagram再整体考察一下改造后的成果。
分享到:
评论

相关推荐

    DAO设计模式笔记.txt

    ### DAO设计模式详解 #### 一、概述 DAO(Data Access Object)设计模式是一种用于封装对数据源(如关系数据库)访问的软件设计模式。它提供了一种将业务逻辑层与数据访问层分离的方法,使得应用程序能够独立于...

    J2EE学习笔记--DAO设计模式基础.txt

    ### J2EE学习笔记——DAO设计模式基础 #### 一、引言 在J2EE(Java 2 Enterprise Edition)开发中,DAO (Data Access Object) 设计模式是一种常用的数据访问层实现方式,用于分离业务逻辑与数据访问逻辑,使得程序...

    web学习笔记 —— DAO设计模式

    通过阅读《318_DAO设计模式.pdf》这份文档,你可以深入理解DAO模式的原理、实现方式以及在Web开发中的应用。同时,文档可能还会介绍一些相关的工具和技术,如MyBatis的使用、Spring的AOP事务管理等,这些都是DAO模式...

    泛型DAO模型设计课堂笔记

    DAO设计模式主要用于数据库操作,将业务逻辑与数据访问逻辑分离。 **一、泛型基础知识** 1. **基本概念**:泛型(Generics)允许在定义类、接口和方法时添加类型参数,用尖括号`&lt;&gt;`表示。例如,`ArrayList&lt;E&gt;`中的`...

    留言管理程序_使用Struts + DAO完成笔记

    【描述】:本文将详细介绍如何利用Struts框架和DAO设计模式来实现一个留言管理程序。这个程序旨在展示如何有效地处理用户提交的留言,并通过数据库存储和检索这些信息,同时遵循MVC(Model-View-Controller)架构...

    JSP+DAO留言管理程序(代码+笔记)

    DAO模式是一种设计模式,用于在应用程序中处理数据访问。DAO对象提供了一种封装数据库操作的方式,使得业务逻辑与数据存储细节解耦。通过DAO,开发者可以编写独立于具体数据库实现的代码,提高代码的可复用性和可...

    使用MVC+DAO完成用户登陆(代码+笔记)

    在IT行业中,MVC(Model-View-Controller)和DAO(Data Access Object)模式是两种常见的软件设计模式,尤其在Web应用开发中被广泛应用。在这个主题中,我们将深入探讨如何结合这两种模式来实现用户登录功能。 **...

    JSP DAO留言管理代码实例 笔记.rar

    首先,我们来看DAO设计模式的核心思想。DAO模式提供了一个接口,该接口定义了对特定数据源进行操作的方法,比如增删改查。在JSP应用中,DAO作为与数据库交互的中间层,使得JSP页面不再直接处理SQL语句,降低了耦合度...

    MVC+DAO_留言管理程序(代码+笔记)

    【DAO设计模式】 DAO(Data Access Object)模式是用于访问数据库或者其他持久化层的接口。在MVC架构中,DAO层通常位于模型部分,负责具体的数据库操作,如CRUD(创建、读取、更新、删除)操作。DAO模式的优势在于...

    JSP+DAO和MVC+DAO(基于MySQL数据库分页)-v笔记

    MVC模式是一种设计模式,它将应用分为模型(Model)、视图(View)和控制器(Controller)。在分页场景中,MVC可以帮助我们更好地组织代码,降低耦合度。 1. **Model**:负责处理数据和业务逻辑,包括DAO的调用,...

    MVC+DAO_留言管理程序(代码+笔记).

    综上所述,"MVC+DAO_留言管理程序"是一个综合运用了MVC设计模式和DAO数据访问模式的Java Web应用实例,它展示了如何通过这两种模式有效地组织和处理Web应用的业务逻辑和数据操作。通过学习这个程序,开发者可以深入...

    留言管理程序_使用Struts + DAO + Hibernate完成笔记

    数据访问对象(DAO)模式是一种设计模式,旨在将业务逻辑与数据访问代码分离。在本项目中,DAO类负责与数据库进行交互,包括添加、查询、更新和删除留言记录。通过使用DAO,我们可以在不修改业务逻辑的情况下更换...

    留言管理程序_使用Struts + DAO + Hibernate完成笔记.zip

    【DAO设计模式】:DAO模式是一种常用的设计模式,它的主要作用是将数据访问操作封装起来,提供一个接口供业务层调用,从而将业务逻辑与数据访问逻辑分离。在本项目中,DAO类负责执行SQL语句,处理与数据库相关的操作...

    设计模式汇总1(多多支持)

    首先,C#设计模式学习笔记可能涵盖了许多设计模式的解释和实例。C#是一种面向对象的语言,设计模式在它的应用中尤为重要。例如,单例模式确保一个类只有一个实例,并提供一个全局访问点;工厂模式提供了一个创建对象...

    李兴华-案例-jsp+DAO实现留言管理程序代码及笔记

    **二、DAO设计模式** DAO模式是Java应用中用于访问数据库的标准模式,它的主要目标是分离业务逻辑层与数据访问层,使得代码更易于维护和测试。DAO类负责执行对数据库的CRUD(Create, Read, Update, Delete)操作,...

    JSP复习笔记——第11章 JSP 构架和MVC设计模式

    本篇复习笔记主要关注的是JSP构架以及MVC(Model-View-Controller)设计模式的应用。 MVC设计模式是软件工程中的一种架构模式,它将应用程序分为三个核心部分:模型(Model)、视图(View)和控制器(Controller)...

    MLDN魔乐JAVAWEB课堂19_Smartupload.swf19_Smartupload.swf

    MLDN魔乐JAVAWEB课堂21_DAO设计模式.swf;MLDN魔乐JAVAWEB课堂25_Servlet程序设计_3.swf;MLDN魔乐JAVAWEB课堂24_Servlet程序设计_2.swf;MLDN魔乐JAVAWEB课堂23_Servlet程序设计_1.swf;MLDN魔乐JAVAWEB课堂18_...

    jspdaoguestbook_java_dao2006com_typical3xe_

    "结合DAO设计模式,通过JSP+DAO修改之前的留言管理程序"表明此项目不仅是一个全新的开发,而且可能是对先前的留言系统的升级或重构,利用DAO模式来优化数据访问层。 【标签】"java dao2006com typical3xe"中的"java...

    项目05_MVC+DAO_留言管理程序(源码+笔记)

    可以供想做留言板的朋友方便查找 本章通过完整的MVC+DAO范例的讲解,充分阐述了J2EE中核心设计模式的应用

Global site tag (gtag.js) - Google Analytics