J2EE项目中的数据持久层设计

时间:2022-07-15 17:06:29

数据持久层的设计目标是为整个项目提供一个高层、统一、安全和并发的数据持久机制。完成对各种数据进行持久化的编程工作,并为系统业务逻辑层提供服务。数据持久层提供了数据访问方法,能够使其它程序员避免手工编写程序访问数据持久层(Persistene layer),使其专注于业务逻辑的开发,并且能够在不同项目中重用映射框架,大大简化了数据增、删、改、查等功能的开发过程,同时又不丧失多层结构的天然优势,继承延续J2EE特有的可伸缩性和可扩展性。

1 数据持久层及ORM映射框架

笔者从事的项目中的数据持久层,是基于J2EE体系结构,并采用了Hibernate作为持久映射框架。

Hibernate是一种新的ORM映射工具,是JDBC的轻量级的对象封装。Hibernate可以用在JDBC可以使用的任何场合,例如Java应用程序的数据库访问代码,DAO接口的实现类,甚至可以是BMP里面的访问数据库的代码。Hibernate不仅提供了从Java类到数据表之间的映射,也提供了数据查询和恢复机制。相对于使用JDBC和SQL来手工操作数据库,使用Hibernate,可以大大减少操作数据库的工作量。

Hibernate是一个和JDBC密切关联的、独立的对象持久层框架,可以搭配各种App Server、Web Server、EJB Container共同使用,Hibernate的兼容性仅同JDBC驱动、底层数据库产品间有一定的关系,但是和使用它的Java程序、App Server没有任何关系,也不存在兼容性问题。而且事实表明Hibernate可以和多种Web服务器或者应用服务器良好集成,如今已经支持几乎所有的流行的数据库服务器(达16种)。

在较为常用的数据持久方案中,Hibernate无疑是最优秀的,下面是对各种持久方案的比较。

¨ 流行的数据持久层架构:

Business Layer <-> Session Bean <-> Entity Bean <-> DB

¨ 为了解决性能障碍的替代架构:

Business Layer <-> DAO <-> JDBC <-> DB

¨ 使用Hibernate来提高上面架构的开发效率的架构:

Business Layer <-> DAO <-> Hibernate <-> DB

我们就上面3个架构来作如下分析。

(1)内存消耗:采用JDBC的架构无疑是最省内存的,Hibernate的架构次之,EB的架构最差。

(2)运行效率:如果JDBC的代码写的非常优化,那么JDBC架构运行效率最高,但是实际项目中,这一点几乎做不到,这需要程序员非常精通JDBC,运用Batch语句,调整PreapredStatement的Batch Size和Fetch Size等参数,以及在必要的情况下采用结果集cache等等。而一般情况下程序员是做不到这一点的。因此Hibernate架构表现出最快的运行效率。EB的架构效率会差的很远。

(3)开发效率:在有Eclipse、JBuilder等开发工具的支持下,对于简单的项目,EB架构开发效率最高,JDBC次之,Hibernate最差。但是在大的项目,特别是持久层关系映射很复杂的情况下,Hibernate效率高的惊人,JDBC次之,而EB架构很可能会失败。

2 数据持久层设计

复杂性是应用开发过程中最令人头疼的一个问题。每当在一个应用中增加一个功能时,它的复杂性通常呈几何级的增长。

这种复杂性往往导致程序的开发无法再继续下去。这也是现在为什么许多应用只有Beta版本而没有正式版的原因。 

专家将应用开发过程产生的复杂性分为两类,即非本质的(accidental)和本质的(essential)。

本质的复杂性是对于解决目标问题所必然产生的复杂性,非本质的复杂性是由于选择了不适当的开发工具和设计工具而产生的复杂性。

对于一个功能确定的程序来讲,本质的复杂性是确定的,而非本质的复杂性则是没有限制的。

因此,一个应用的开发要想较顺利地取得成功,就需要尽可能地减少非本质的复杂性。

设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式,

也会使新系统开发者更加容易理解其设计思路。

衡量一个系统优秀与否的关键因素,除了能够满足用户需求外还有如下方面:首先是灵活性。

灵活性意指这种结构或模式不依赖于任何实际应用,应该与操作系统、应用程序无关。提供独立的结构,可以提供最大的重用。

其次是可扩展性。随着业务的扩展,新的业务不断增加,业务逻辑自然增加,系统必然会进行修改或添加相应功能模块。再次是可配置性。最后是安全性。

数据持久层的设计采纳了多种设计模式,最大限度的降低了系统内部各模块、子系统间的耦合性,使得系统相对易于扩展,

并且能够在进行改变时,保证持久层的业务逻辑层相对稳定,基本不需要因持久层的调整改变而进行逻辑层的变动。

笔者在项目中采用了如下设计模式。

2.1 整体架构——MVC模式(模型-视图-控制器)

¨ 模型(Model):模型包含完成任务所需要的所有的行为和数据。在数据持久层中,模型即为值对象以及数据访问对象。

¨ 视图(View):数据持久层中,视图就是持久层同其它层进行数据交换的值对象(Transfer Object)和视图助手对象。

¨ 控制器(Controller):持久层所需的控制相对简单,因此集成到了控制代理中。

持久层整体采用MVC模式,使得整个数据持久层的实现部分与项目的业务逻辑部分隔离开来,

能够实现对接口作大的修改而不需要对相应的模型进行修改。另外,持久层某子系统发生变化时,不会影响到其它子系统。

有利于提高系统的稳定性、可维护性。

2.2 值对象模式(Value Object Pattern)

值对象用来封装业务对象。相应的方法调用是设置(getter)和检索(setter)值对象。它是任意的可串行化的Java对象,

当客户端Bean请求业务数据时,该Bean可以构造值对象,用属性值来填充,并按照值把它传递给客户端。

在笔者开发项目的数据持久层体系结构中,值对象主要应用在子系统间传递、

交换数据(Transfer Object)和映射数据表两个方面(Persistent Object)。

在各子系统间进行数据传递和数据交换时,使用值对象模式能够最大化地降低系统间数据传递的开销。

在这种策略下传递的是对象而不再是一个个的有意义的数据,使得系统在进行扩充、修改时,

各子系统间数据传递部分不会受到影响,因为各子系统仅需要关心是否有值对象被传递,而并不去关心传递的到底是什么数据。

在映射数据库表时,值对象类及其子类所构成的树形结构被用来映射一个数据库表,该继承树通过XML配置文件对应数据库中的单个表,

这使得最底层的关系型的数据库表结构能够面向对象模型所隐藏,另外,由于面向对象设计方法中类的可继承性,

采用继承树对应一个表的策略使得该映射策略极易扩展,并且能够将一个复杂的数据表转化成若干简单的值对象来表示,

提高了系统的可维护性和可修改性。

2.3 数据访问对象(DAO)

根据数据源不同,数据访问也不同。根据存储的类型(关系数据库、面向对象数据库等)和供应商不同,

持久性存储(比如数据库)的访问差别也很大。当业务组件或表示组件需要访问某数据源时,它们可以使用合适的API来获得连接性,

以及操作该数据源。但是在这些组件中包含连接性和数据访问代码会引入这些组件及数据源实现之间的紧密耦合。

组件中这类代码依赖性使应用程序从某种数据源迁移到其它种类的数据源将变得非常麻烦和困难,当数据源变化时,

组件也需要改变,以便于能够处理新类型的数据源。

笔者开发项目的数据持久层使用数据访问对象(DAO)来抽象和封装所有对数据源的访问。

DAO管理着与数据源的连接以便于检索和存储数据,DAO实现了用来操作数据源的访问机制,

内部封装了对Hibenernate数据操纵、事务处理、会话管理等API的封装。

外界依赖于DAO的业务组件为其客户端使用DAO提供了更简单的接口,DAO完全向客户端隐藏了数据源实现细节。

由于当低层数据源实现变化时,DAO向客户端提供的接口不会变化,采用该设计模式允许DAO调整到不同的存储模式,

而不会影响其客户端或业务组件,即使将来不再采用Hibernate作为关系映射框架,上层客户端也不会受到任何影响。

另外,DAO还充当组件和数据源之间的适配器的角色。

数据持久层通过调整抽象工厂(Abstract Factory)模式和工厂方法(Factory Method) 模式

(这二个创建型模式的实现详情参见GoF的<设计模式>),),使DAO模式达到了很高的灵活度。

当底层存储随着实现的变化而变化时,该策略可以通过使用抽象工厂模式实现。抽象工厂可以基于工厂方法实现而创建,

并可使用工厂方法实现。该策略提供一个DAO的抽象工厂对象,其中该对象可以构造多种类型的具体的DAO工厂,

每个工厂支持一种不同类型的持久性存储实现。一旦你获取某特定实现的具体DAO工厂,可以使用它来生成该实现中所支持和实现的DAO。

2.4 连接池、应用级缓存及享元模式(提升系统性能)

¨ 缓存(Cache)

对于数据库来说,厂商的做法往往是在内存中开辟相应的区域来存储可能被多次存取的 数据和可能被多次执行的语句,

以使这些数据在下次被访问时不必再次提交对DBMS的请求和那些语句在下次执行时不必再次编译。

同样,数据持久层采用缓存技术来保存已经从数据库中检索出来的部分常用数据。客户端访问持久层时,

持久层将首先访问缓存,如果能够命中则直接从缓存中提取数据,否则再向数据库发送提取数据的指令。

这种设计能够大幅度地提高数据访问速度。

¨ 连接池(Connection Pool)

池是一个很普遍的概念,和缓冲存储有机制相近的地方,都是缩减了访问的环节,但它更注重于资源的共享。

对于访问数据库来说,建立连接的代价比较昂贵,因此,数据持久层建立了“连接池”以提高访问的性能。

数据持久层把连接当作对象,整个系统启动后,连接池首先建立若干连接,访问本来需要与数据库连接的区域,

都改为和池相连,池临时分配连接供访问使用,结果返回后,访问将连接交还。这种设计消除了JDBC与数据源建立连接的延时,

同时在应用级提供了对数据源的并发访问。

¨ 享元模式(Flyweight)

面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,数据库中的记录,

如果以每条记录作为一个对象,提取几千条记录,对象数就是几千,这无疑相当耗费内存。

数据持久层依据享元模式设计了若干元类,封装可以被共享的类。这种设计策略显著降低了系统的内存消耗。

2.5 各种对象的创建模式—工厂方法(Factory Method)

工厂方法模式将创建实例的工作与使用实例的工作分开,也就是说,

让创建实例所需要的大量初始化工作从简单的构造函数中分离出去。只需要调用一个统一的方法,

即可根据需要创建出各种对象的实例,对象的创建方法不再用编码到程序模块中,而是统一编写在工厂类中。

这样在系统进行扩充修改时,系统的变化仅存在于工厂类内部,而绝对不会对其他对象造成影响。