DAO模式

DAO(Data Access Object)模式实际上是两个模式的组合,即DataAccessor 模式和 Active Domain Object 模式。

其中DataAccessor模式实现了数据访问和业务逻辑的分离,而Active Domain Object 模式实现了业务数据的对象化封装,一般我们将这两个模式组合使用。

Data Accessor模式实现了数据访问和业务逻辑的分离:就是将数据访问的实现机制加以封装,与数据的使用代码相分离,从外部来看,Data Accessor 提供了黑盒式的数据存取接口。

Domain Object模式实现了面向领域的业务数据对象化封装。

比如说Person对象,就是对现实业务领域的一个抽象,拥有相关属性get/set的JavaBean。

 

Target

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

1. 数据存储逻辑的分离

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

2. 数据访问底层实现的分离

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

3. 资源管理和调度的分离

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

4.数据抽象

在直接基于JDBC调用的代码中,程序员面对的数据往往是原始的RecordSet数据集,诚然这样的数据集可以提供足够的信息,但对于业务逻辑开发过程而言,如此琐碎和缺乏寓意的字段型数据实在令人厌倦。

DAO 模式通过对底层数据的封装,为业务曾提供一个面向对象的接口,使得业务逻辑开发员可以面向业务中的实体进行编码。通过引入DAO模式,业务逻辑更加清晰,且富于形象性和描述性,这将为日后的维护带来极大的便利。试想,在业务曾通过Customer.getName方法获得客户姓名,相对于直接通过SQL语句访问数据库表并从ResultSet中获得某个字符型字段而言,哪种方式更加易于业务逻辑的形象化和简洁化?


Demo

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

public BigDecimal calcAmount(String customerID, BigDecimal amount) {
        Customer customer = CustomerDAO.getCustomer(customerID);
        Promotion promotion = PromotionDAO.getPromotion(customer.getLevel());
        Customer.setSumAmount(customer.getSumAmount().add(amount));
        CustomerDAO.save(customer);
        return amount.multiply(promotion.getRatio());
}

这样的代码相信已经足够明晰,即使对于缺乏数据库技术基础的读者也可以轻松阅读。

从上面这段代码中,我们可以看到,通过DAO模式对各个数据库对象进行封装,我们对业务层屏蔽了数据库访问的底层实现,业务层仅包含与本领域相关的逻辑对象和算法,这样对于业务逻辑开发/维护人员而言,面对的是一个简洁明快的逻辑实现结构。业务层的开发和维护将变得更加简单。

 

Improve

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

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

 

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

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

 

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

 

Factory

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

对于例子中的CustomerDAO而言,我们可以抽象出DAO接口,然后不同的数据库Accessor来实现这个接口,通过DAOFactory来根据配置文件自动分配。

public interface CustomerDAO {
    public Customer getCustomer(String customerID);
    public void save(Customer customer);
}
public class CustomerDAOImpOracle implements CustomerDAO {}
public class CustomerDAOImpMysql implements CustomerDAO {}
 

然后在xml配置文件中定义好映射关系,在获取实例的时候就会自动加载:

CustomerDAO custDAO = (CustomerDAO)DAOFactory.getDAO(CustomerDAO.class); 

<sectionname="DAO_MYSQL">

    <entrykey="com.jscai.www.dao.CustomerDAO"

        value="com.jscai.www.dao.imp.mysql.CustomerDAOImpMysql"/>

    <entrykey="com.jscai.www.dao.PersonDAO"

        value="com.jscai.www.dao.imp.mysql.PersonDAOImpMysql"/>

</section>

<sectionname="DAO_ORACLE">

    <entrykey="com.jscai.www.dao.CustomerDAO"

        value="com.jscai.www.dao.imp.oracle.CustomerDAOImpOracle"/>

    <entrykey="com.jscai.www.dao.PersonDAO"

        value="com.jscai.www.dao.imp.oracle.PersonDAOImpOracle"/>

</section>

 

         通过上面的代码我们可以看到,通过接口我们将具体的DAO实现从代码中分离。也就是说,业务层通过接口调用底层实现,具体的DAO实现类不会出现在我们的业务代码中。

         而且如果需要切换底层实现,只要切换一个配置项就可以了。

         如果Hibernate和Spring集成后,这个DAOFactory可以通过Spring的BeanFactory来替换,通过DI来把相应的DAO实现传入。如果需要切换底层实现,只需要换一个配置文件。

         另外,在我们的DAOFactory中还可以做一些优化,比如说DAO可以做成Singleton的模式,反正DAO中没有特有的属性,可以全局共享一个instance。

 

Proxy

         加上DAOFactory之后,代码灵活性提高了很多,但是似乎出现了一些Bad Smell,相对于改造前的calcAmount方法,这段代码里混杂了一些数据访问层的内容,如DAOFactory.getDAO方法的调用,这样影响业务逻辑代码的可读性。

         calcAmount方法应该只有业务逻辑代码,而关于DAO的获取是不同层次的操作,影响了代码整洁性和逻辑的纯粹性。我们可以加入一个中间层 (Proxy) 来消除。

public class CustomerDAOProxy {
    private static CustomerDAO dao;
    public static CustomerDAO getDAO() {
        if (dao ==null) {
            dao = (CustomerDAO) DAOFactory.getDAO(CustomerDAO.class);
        }
        returndao;
    }
    public static Customer getCustomer(String customerID) {
        returngetDAO().getCustomer(customerID);
    }
    public static void save(Customer customer) {
        getDAO().save(customer);
    }
}

         这样我们的业务逻辑层又能恢复简洁性和可读性了:

Customer customer = CustomerDAOProxy.getCustomer(customerID);


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值