在软件项目的分析设计过程中,我们首先分析数据实体,例如确定类,类成员变量或者画ER图。再详细设计UI界面上有哪些输入框,文本框等,紧接着我们还会确定方法的参数个数和类型。这些过程紧密地依赖于数据实体的稳定性,比如在数据库设计中,我们需要多少表,每个表的字段有多少,它们的类型是什么等。但是当这个稳定性失去了怎么办?用户很有可能说目前我只能为我的表大概确定这些字段。项目组是否该等到用户确定之后再做?如果用户说字段的变化就是我的一个需求,项目该如何开发?即使所有客户能确定字段,不同的客户确定的字段可能不会是一样的。由于不同的客户对字段的需求不是一样的,项目组有时不得不不厌其烦地构造源代码的版本数。本文基于java环境,分析和实现了解决这个问题的方案。首先指出j2ee容器管理持久性实体bean的不足,接着讲述了用java实现这个需求的技巧,最后是具体地实现。
固定字段假设和CMP实体BEAN类机制
CMP实体BEAN机制也就是容器管理持久性实体bean机制。CMP实体bean的提供者提供的bean类具有持久性字段(或属性)的抽象get/set方法。这两个方法与普通java bean的属性的get/set方法一样。下面是一个Personbean实体bean类的name持久性字段的申明。
- Class Personbean extends EntityBean {
- Abstract String getName();
- Abstract String setName(String vname);
- String ebjCreate(String name) { setName(name);};
- ------
- }
- 部署时,一般部署工具会产生这个类的子类,子类的申明大概如下:
- Class PersonbeanSubClass extends Personbean{
- Private:
- String name;
- Public:
- String getName(){ return name;}
- String setName(String vname){name=vname;}
- ------
- }
至于具体的字类实现机制可参见《Mastering Enterprise JavaBeans Second Edition》。容器创建的是子类的实例。通过父子类的比较可知,子类通过一个私有字段和继承的两个属性get/set方法实现了一个实体bean的持久性属性。部署工具是根据java bean的内省机制生成这个子类的。这样bean提供者只需规定持久性字段的抽象访问器函数,其他的持久性实现都有工具辅助完成。但我们必须注意到,为了指定一个持久性字段,提供者必须硬编码两个访问方法。同样我们注意到为了创建一个实体Bean,我们为ejbCreate方法提供了一个类型为String的参数。这样的代码无疑建立在这个实体bean只有一个持久性字段的前提之下。类似假设下的语句还有访问数据库时的Statement语句:
- Statement st = conn.createStatement();
- St.execuate("insert into person (name) value('John')");
广泛使用类似假设的例子还有Struts的视图-模型数据交换机制中ActionForm和HTMLTag定制标签处理类的数据交互。我暂且称这种假设为固定字段假设,基于这个假设的代码实现机制为CMP实体BEAN类机制,目的在于重视j2ee中的这个特征。
不定字段假设和脚本语言类技术
固定字段假设和CMP实体BEAN类机制硬编码持久性字段,把字段的名字,个数和类型(本文称为持久性字段的三属性)三个中至少一个固定下来了,使得更改持久性字段的工作必然影响源代码,这就产生了一系列令人讨厌的代码树。不定字段假设和脚本语言类技术就是要把持久性字段的三属性和源代码分开,最终达到客户可以订制持久性字段的目的。典型的实现技术有XML,动态编译技术,元数据技术,字典集合技术等。这些技术有一个共同点就是不固定持久性字段,有一个持久性字段的数据容器和一部分分析代码。分析代码解释数据容器中的持久性字段,最后执行数据库操作。动态编译技术是一个过渡技术,它可根据客户配置,动态生成源代码,接着及时编译生成字节代码,部署到应用中。
3.1 XML
XML是一个非常好的数据交换格式,它具有很好的模式定义(DTD),DTD是XML文档的元数据,定义了文档中数据的格式和组成。XML文档中同时包含了数据名称(元素名或属性名)和数据值(元素文本或属性值)。另外JAVA中有很强的XML文档分析和使用API,包括JAXP,JAXM等。JAXP集合了基于事件分析的简单XML编程接口SAX和节点数的DOM分析技术。JAXM则是利用XML进行消息发送接收和消息处理的编程接口。XSLT能很容易地把XML文档转为其他格式的文档如HTML,JAVA源代码等。
3.2 动态编译技术
利用XML表达用户配置信息,XSLT把这些信息转换成相应的JAVA源代码,接着是用java.lang.Compiler类及时编译产生字接代码。当然你也可以生成其他的辅助类,sql语句等。详细描述请参照 http://www.javaworld.com/javaworld/jw-02-2002/jw-0201-xslt.html
3.3 元数据技术
元数据技术把关于数据的描述放在数据字典中,使用者访问数据字典可以得到关于数据的信息。数据字典可以放在xml文档中,也可以在数据库服务器上。在客户配置了持久性字段后,开发者访问数据字典可以得到客户的当前持久性字段,并生成正确的代码。
3.4 字典集合技术
java中的哈西表等字典类集合数据结构可以在方法调用之间传递变化的持久性字段。平常我们的方法调用是表中有多少字段,填充数据库的函数一般要接收多少参数,这样就把持久性字段硬编码入了源代码中,持久性字段变化必会造成源代码的变动。字典集合技术使这样的函数的接口是固定的。
一个简单任务
为了应用上面的分析,具体体现如何实现与数据库表字段松散耦合的j2ee应用,在这里提出一个简单的任务:做一个采集人员信息的应用程序。
我们粗略分析一下便可得到一个人员类,暂且命名为Person,但字段我们确定不了。采用WAF框架来设计。关于WAF框架可参见 http://www.ibm.com/developerworks/cn/java/l-j2eeArch/index.shtml
4.1 设计一、字典集合技术和元数据技术
下面设计图(图一)表示:客户发出http请求,容器定位到person.jsp,这个网页分成服务器部分和客户端部分,服务器部分为在容器中运行的指令,这些指令会build在客户浏览器上显示页面的客户端部分,客户端部分聚集(包含)了一个html表单,表单有一个提交按钮,客户可以点击此按钮发出提交请求。根据WAF的框架流程,我们设计一个personHTMLAction的类来处理用户的提交请求。下面是这个关键类的设计说明:字段:
Connection conn 保存了从容器连接池中获取的数据库连接;
Hashtable reqHashNameValue 保存了从用户提交的表单中提取的名字-值对;
Hashtable targetHashNameType 保存了从数据库中获得的关于数据表persion_table的元数据--字段名-类型对;
Hashtable finalHashNameValue保存了最后插入到数据库中的名字-值对;
函数或方法:
Connect getConnect() 从容器的连接池中获取数据库连结;
Hashtable getReqHashNameValue() 从用户提交的表单中提取名字-值对;
Hashtable getTargetHashNameType() 从数据库中获得关于数据表persion_table的元数据--字段名-类型对;
Hashtable getFinalHashNameValue() 根据targetHashNameType中的字段过滤掉reqHashNameValue中过多的字段,
得到最后插入到数据库中的名字-值对;
Void insert()根据targetHashNameType中的类型和finalHashNameValue中的名字-值对构造sql语句,操作数据库。
这些函数统一由WAF框架中的这个类的父类HTMLAction的一个函数perform来调用。
图二表达了这个设计达到的松散耦合效果。第一处在表单和处理类之间,它们间的数据传递充分利用了哈西字典类。达到的直接好处是我们可以开发出定制表单的工具让客户自己定制应用的输入界面,客户可以增加各种输入元素到表单上却不会影响后台的处理类。第二处在处理类和数据库表格之间,它们间的数据传递充分利用了数据库中的元数据信息,达到的直接好处是我们可以开发出定制数据表的工具让客户自己定制数据表的多数字段,客户可以增加减少或修改字段却不会影响处理类。
4.2 设计二、xml技术、哈西技术和元数据技术
设计图(图三)于图二不同的是我们在控制层内部加上了JMS技术,用XML作为数据的交换格式。 XMLpersonHTMLAction的类来处理用户的提交请求,PersonMDB把数据插入到数据库中去。下面是这两个关键类的设计说明:
XMLpersonHTMLAction类
函数或方法:
String getReqXML() 调用getParameters()获得客户的提交数据,产生xml文档;
Void sendXML() 生成一个临时队列作为消息的反馈队列,利用JMS API把getReqXML()返回的xml文档作为JMS的消息体发送出去。
PersonMDB类
字段:
Connect conn 保存了从容器连接池中获取的数据库连接;
Hashtable XMLHashNamue 保存了从处理类的发送来的消息中提取的名字-值对;
Hashtable targetHashNameType 保存了从数据库中获得的关于数据表persion_table的元数据--字段名-类型对;
Hashtable finalHashNamue保存了最后插入到数据库中的名字-值对;
函数或方法:
Connect getConnect() 从容器的连接池中获取数据库连结;
Hashtable getXMLHashNamue() 从处理类的发送来的消息中提取名字-值对;
Hashtable getTargetHashNameType()从数据库中获得关于数据表persion_table的元数据--字段名-类型对;
Hashtable getFinalHashNamue()根据targetHashNameType中的字段过滤掉XMLHashNamue中过多的字段,得到最后插入到数据库中的名字-值对;
Void insert()根据targetHashNameType中的类型和finalHashNamue中的名字-值对构造sql语句,操作数据库;
Void sendReply() 发送反馈消息给处理类。
这些函数统一由EBJ 2.0 中的消息驱动BEAN的onMessage函数统一调用。
图四表达了这个设计达到的松散耦合效果。与图二相比,这个设计增加了一个松散耦合,从而增强了设计的分布特性。
总结
不能确定数据库表的字段是一个普遍的需求不确定性问题,本文通过对J2EE技术的分析总结出两个假设:固定字段假设和不定字段假设。有好多关键技术是基于固定字段假设的如CMP的实体BEAN技术,众多框架的视图-模型数据交换技术。这个假设和基于这个假设的技术往往造成项目的需求不确定风险,而且往往使项目组生成枝叶繁盛的源代码版本数。不定字段假设把这种不确定性作为一个需求来处理,利用了XML技术、集合技术、元数据技术甚至动态编译技术解决这个问题,达到数据库字段和应用松散耦合的最终目的。本文还给出了两个设计方案供参考。
【编辑推荐】