Java类封装关系数据库中数据访问的通用方案
2008-12-19
作者:彭德云* 王嘉祯* 陈卫**
1 引言
目前的数据库管理系统" title="管理系统">管理系统(DBMS)中基本上是关系型数据库(RDBMS)占据着主导地位。从形式上来说,关系数据库" title="关系数据库">关系数据库是一系列行列规整的表的集合,但逻辑上数据库中数据是对现实世界中事物的抽象描述。而面向对象" title="面向对象">面向对象建模的过程也是对事物的抽象过程。所以关系数据库中的数据和对象二者间存在一种以其共同描述的现实世界中的事物为纽带的联系。利用面向对象思想对关系数据库中的数据访问" title="数据访问">数据访问进行抽象和封装,可以有效地提高系统性能、降低开发成本。本文对传统的数据库访问技术的用Java类进行了面向对象的抽象和封装,并就其中目前广泛存在的问题" title="存在的问题">存在的问题提出了一种通用的解决方案,极大地提高了开发效率。
2 基本的面向对象的封装方法
面向对象思想是目前最为流行的设计思想。充分利用面向对象的思想,对数据库数据访问进行抽象和封装,将带来以下几个好处:①对数据和过程进行抽象封装,提高了系统的模块独立性,降低了系统的耦合度;②对相关实体的封装,有利于构件复用;③信息隐蔽,减少发生错误时副作用的传播;④继承性使得对超类的修改可以立即作用到所有子类。
2.1 实体类
关系数据库数据访问操作,可以利用面向对象的思想进行封装,可以建立“表(或视图等)—对象”的对应关系,把数据的取、增、删、改操作封装成类方法。
在数据库中,大量的数据访问许多是对表最基本的数据库操作(对记录的取、增、删、改操作)或这些操作的组合。关系数据库中的表可以映射为类,表的字段可以映射成类的属性,对表的操作可以映射成类的方法,表的记录(行)可以映射成类的对象。现在以一个简化的学校信息管理系统为例说明数据的访问过程。图1中每个方框表示一个实体,在数据库中每个实体用一个表表示(见图2)。

如上述的学校管理系统中就可以建立学校类、学生类、教师类、课程类等。一个实体类的属性通常以对应的表的字段命名;其方法通常是以对某个字段信息的操作命名。比如学生类有学生编号、姓名、性别等属性,有取编号、设置编号等以getXX和setXX的方法(XX对应于某一属性)。
2.2实体管理类
在对“实体”(比如学生Student)抽象和包装的过程中,往往需要一个对应的“实体管理类”负责对实体的管理工作。因为“实体对象”本身侧重于对单个实体的描述,而数据库访问通常是对一组满足特定条件的记录(对应于一组实体对象)进行操作,所以需要在“YY实体” 层(YY表示某类实体名)之上有一具有对应管理功能的类层次,称之为“YY实体管理类”。比如“学生类”(Student)的管理类为“学生管理类”(StudentManage)。实体管理类主要的方法对应于相应实体的查找、增加、删除、修改、列表等操作。一个实体管理类的方法通常是一组有getYY、addYY、deleteYY、editYY、listYY特征的成员函数。
假设现需查询在特定成绩段的学生,就可以调用学生管理类的以成绩段为参数的类方法得到所有满足查询条件的学生,如果想得到这些学生的信息,则可以调用学生对象的“getXX”方法实现。下面以学校管理系统的学生实体为例进行抽象和封装。
public abstract class Student{//学生类
String id = null;//学生编号
String name = null;//学生姓名
…
public abstract void setId(String _id);//设置学生实体编号
public abstract String getId();//取学生实体编号
...
}
下面这个类是相应的一个学生管理类的代码:
public class StudentManage{//学生管理类
public Student findStudentById(String _id) {…}//查找某一编号的学生
public boolean deleteStudentById(String _id) {…}//删除某一编号的学生
...
}
2.3 存在问题
可以看出,通过以上的对学生信息相关操作的封装,对学生信息的程序处理逻辑上更加清晰,同时也提高了软件的复用程度,降低了系统的偶合度。但是该模式也有明显的弊端:①由于实际系统中大量表的存在,要编写大量相应的封装类(实体类和相应实体的管理类),封装类的个数随着表的数量(N)成约为N*2的比率增长; ②封装类的大小(主要是成员变量和成员函数的个数)随着表的字段个数(M)的增加成约为M*λ(λ为封装类的平均成员函数个数)的比率增长;③对数据库中表结构的变动会传播到类的定义上来。
3 通用解决方案
3.1解决思路
上面所提出的抽象和封装方案问题在于实体的特征和功能(对应于类的属性和方法)是静态的:实体的特征和功能在类的定义时就确定下来了(以字段命名),在程序的运行过程中也无法动态改变或更新。这样就必须把几乎所有的字段及其操作都对应成类的属性和方法。通用解决方案就是试图找到一条能够克服以上弊端的途径。其核心思想是将数据库表及其字段信息存储在数据库中,动态地创建实体。该实体类的定义不与特定的表关联,但能与某个表动态关联并执行特定的功能。
该方案的实现有两个问题必须解决:
①动态创建逻辑实体。动态创建逻辑实体需要在给定实体名的条件下能获得该实体在特定条件下所拥有的特征。实体特征实际上是一个<表名,字段名>的二元组。目前的解决方法是在库中预先创建一个实体特征表,有三个基本字段:表名、字段名、字段类型。
②动态赋予逻辑实体的特征和功能。目前的解决方法是把实体的特征和特征类型用哈希表(Hashtable)的形式存储在类中。实体特征的增、删可以通过对哈希表的操作(put和remove)实现。一个实体属性一般有两个实体方法与之对应,即取、设置该属性值的操作。如果把属性名称作为实体方法的参数以标识对某个属性值的操作,这样就可以避免为每个属性值都要编写对应的方法(getXX和setXX)。
下面就以在学校信息系统中的应用进行说明。在库中建立一实体特征表UNIT_ATTR,有三个字段:实体名(TABLE_NAME),实体属性(ATTRIBUTE_NAME),属性类型(ATTRIBUTE_TYPE)。定义两个类GerneralUnit和GerneralUnitManage。
3.2通用实体类
下面是对通用实体类的定义:
public abstract class GerneralUnit {//通用实体类
protected Hashtable attributes = null;//实体的特征(属性-属性值)存储在哈希表中
protected String unitName = null;//实体(表)名
protected Hashtable field_type =null;//(属性—属性类型)
public abstract String getStringAttribute(String fieldName);//取某个String类型的属性值,属性名作为方法的参数
public abstract void setStringAttribute(String fieldName,String value);//设置某个String类型的属性值,属性名作为方法的参数
...
}
通过这种方式,可以将实体的方法与数据库的表的字段名动态地关联起来,其中一个好处就是避免了2节中的问题②③,实体的方法不会随着关系表的增长而增长,其变动对实体类的定义没有影响。更重要的一个好处是这种动态关联使得该实体类成为通用的实体类,这个通用实体类可以随关系表的不同,可能代表“学生类”、“教师类”、“课程类”、“工资类”等等,解决了问题①。
3.3通用实体管理类
下面是对通用实体管理类的定义:
public class GerneralUnitManage {//通用实体管理类
//通过实体主特征查找实体,并返回动态创建的实体对象
public GerneralUnit[] getUnits(String unitName,Hashtable mainAttribute){…}
public deleteUnit(String _UnitName,Hashtable mainAttribute){…}
...
}
在实体管理类的方法中,对数据库的操作基本上分为两种情况:一种是从数据库关系表中取数据,并根据这些数据为实体属性赋值;另一种是根据实体的属性值对关系表的记录进行添加、修改或删除。通用性是通过获取数据库中表及其字段的相应信息,并根据这些信息动态赋予实体相应属性值(或根据动态赋予实体的属性值来进行相应的数据库操作)来实现的。下面以GerneralUnitManage类中的查找实体方法getUnit为例进行说明:
public GerneralUnit[] getUnits(String unitName,Hashtable mainAttribute) {//参数:实体名、关键特征
…
String strSQL = "select ZDMC, SJLX, ZDHY from VVVV where BMCV='" + unitName + "'";//查找关系表的所有字段
while(rs.next()) {
String key = rs.getString("ZDMC");//将实体与关系表关联起来
…
info.setAttributeType(key,dataType);
…
}
String findSQL =…//根据查找实体属性生成SQL语句
ResultSet rs = stmt.executeQuery(findSQL);//执行生成的SQL语句,根据结果集生成相应实体
while(rs.next()){
…
for (i = 0; i < count; i ++ ) {//为实体赋予属性值
key = keysInDb.get(i).toString();
unit.setAttribute(key, rs.getObject(key));
units.add(unit);
}
}
…
if(bSuccessToGet)return units;
else return null;
}
因为能将实体和关系表动态关联,该实体管理类具有通用性。通过对其方法中的参数设置将不同的关系表与通用实体类关联起来,从而避免了2节中提到的类的数量随数据库中关系表的数量正比增长的问题。
该方案是针对一般情况而设计的。对于某些特殊应用,可以将上述通用方案的某些方法进行重载以满足部分特殊需求。
4 结束语
本文针对面向对象的关系数据库数据访问实现中存在的问题,提出了一种新的通用方案。应用该方案可以一次性编写两个通用的类代替原来的诸多实体类和实体管理类,代码量大大减少。并且这两个类可以几乎无需修改地应用到其他系统中。该方案已经在多个军队自动化系统的开发中得到实际应用。虽然本文是以三层结构模型下的信息系统开发为例进行说明的,但是该方案对两层结构模型下的开发也有一定的借鉴意义。
参考文献
1、 唐潜、杨德华,用JAVA类封装RDB库表──在关系数据库上运用OO技术探讨,计算机应用研究,1999.11
2、 Scott W. Ambler,Mapping objects to relational databases,http://www-900.ibm.com/developerWorks/cn/components/mapping-to-rdb/index_eng.shtml,2000.7
3、 王建华等译,最新Java2核心技术(卷Ⅱ:高级性能),机械工业出版社,2003.1
