以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 DOM/SAX/XPath 』  (http://bbs.xml.org.cn/list.asp?boardid=11)
----  [原创]对XML文档操作的通用CRUD(JDOM版)  (http://bbs.xml.org.cn/dispbbs.asp?boardid=11&rootid=&id=29092)


--  作者:jomper
--  发布时间:3/22/2006 7:54:00 PM

--  [原创]对XML文档操作的通用CRUD(JDOM版)
/**
*1.操作XML文档分析
*2.设计过程
*3.适用范围
*/


关键词:元组元素 元组元素相当于关系数据库里元组的概念。

1.0 操作XML文档分析
1.1 为什么要写XML文档操作的通用CRUD(Create Read/Retrieve Update Delete)。
做过几次读写改删XML文档的工作,总是被大量的element构建弄的头晕眼花。XML文档简单点还好,一旦元素属性变得复杂,整个XMLDAO模块就会变的相当臃肿。导致程序的可读性下降,bug增多。而且每个XMLDAO的CRUD都是做的同一个流程,完全可以独立出来。

1.2 XMLDAO的CRUD分析
在XML的CRUD操作中有几个功能是会反复重复的。
(1)Input:读XML文档,并用SAX或者DOM解析成Document对象。
(2)Find:用XPath在文档中查询元素,并返回查询结果。其实在操作XML数据的时候Find跟Read基本是一个概念,查询数据后当然是要返回一个查询结果的,不然好浪费资源。这里结果就是一个list。
(3)Construts:构建需要操作的Element。包括构建新元素,更新元素或属性的值,删除元素。为什么不是Document对象?其实没什么区别。
(4)Output:写Document对象到XML文档。

唯一有区别的地方是Construts Element。对于不同的XML文档存在着不同Element上下文。我要做的就是把除Construts Element之外的工作独立出来。

Create操作 需要Construts Element和输出Document。
Read操作 需要读取XML文档,并返回一个读取结果。
Retrieve操作 需要读取XML文档并给特定的Element添加一个子元素。一般这个特定的元素就是元组元素的父亲。元组元素相当于关系数据库里元组的概念。
Update操作 需要读取XML文档,然后通过XPath获得查询结果并重新构建这个结果。最后输出到文档。
Delete操作 需要读取XML文档,然后通过XPath获得查询获得需要删除元素。最后在上下文中删除它。

下面是每个操作的工作流程:
                 Create     Read      Retrieve      Update     Delete
Input                      √           √              √         √
Find                       √           √              √
Construts         √                    √              √
Output            √                    √              √

2.0 设计过程
2.1 XML文档设计(XML文档设计的一点建议)
2.1.1 建议一 尽量少用elements来装载数据,无论什么时候都尽可能用attributes来代替
根据《Java Enterprise Best Practices》- 5.1.3 Use Elements Sparingly, Attributes Excessively的内容,“ use elements infrequently and, instead, use attributes whenever possible. ”尽量少用elements来装载数据,无论什么时候都尽可能用attributes来代替。这样做的好处是针对SAX的处理机制做出了优化。可能你要说你从不使用SAX,你有其他的解决方案例如:DOM,JAXP等等。事实上这些高层的API例如:DOM,JAXP,JDOM,SOAP在底层还是使用的SAX,如果你不相信你可以直接去看它们的源码。

一个XML文档的例子:
例2.1:
<?xml version="1.0" encoding="UTF-8"?>
<JomperWorld>
 <userInfo userCode="-1426920836">
  <accountInfo userId="jomper1" password="888888"></accountInfo>
  <basicInfo nickName="jo" gender="男" placeOfBirth="hubei" email="1@1.com" registerDate="3-17-2006" picture="null"></basicInfo>
  <detailInfo>
   <experienceOfTeaches><![CDATA[test12]]></experienceOfTeaches>
   <individualHonor><![CDATA[test22]]></individualHonor>
  </detailInfo>
 </userInfo>
</JomperWorld>

2.1.2 建议二 给你的元组元素确定一个KEY
这样设计应当还注意一个问题,特别是你可能会使用XPath。
就看例2.1,如果元组元素<userInfo/>没有一个userCode属性。那么已知accountInfo.userId想查询basicInfo.nickName 的XPath就必须这样写 "/JomperWorld/userInfo[accountInfo/@userId='jomper1']",但是得到的是<userInfo/>而不是期望的nickName属性。必须userInfo.getChild("basicInfo").getAttribute("nickName");才能获得nickName属性。
当然这不会给设计带来致命的打击,但多少会有些不便,这个不便具体有多少只有你自己知道。

2.2 Construts element类的设计

2.2.1  Builder接口设计
由于将给不同的XML文档构建元组元素,所以这个接口是通用XMLDAO的关键。
需要Construts Element的操作包括create,retrieve,update。
例2.2:
 public interface Builder {
  /** builderType */
  public static final int USER_INFO_BUILDER= 0;
  //更多的builderType......

  /**
  * create&retrieve构建元素
   * @param toggle create&add判断开关
   * @param facade elements外观
   * @return 元组元素
   */
  public Element addBuilder(boolean toggle,Object facade);
 
  /**
   * update构建元素
  * @param facade elements外观
  * @param mainElement 元组元素
   */
  public void updateBuilder(Object facade,Element mainElement);
 }
builderType 是用来在XMLDAO里判断facade的对象类型。本来可以用instanceof做迭代判断最后确定其对象类型,但这样效率太低。于是传一个参数来确定。不知道还有没有更好的办法。
facade 可以是一个元素的DTO(元组元素很简单的情况),也可以是几个元素DTO的外观。可以直接通过它获得element的数据,也可以通过它间接获得其他元素DTO的数据。


2.2.2 ConcreteBuilder类的设计
例2.3:
public class UserInfoBuilder implements Builder{
 private Element rootElement,userInfoElement;
 
 UserInfoFacade userInfo;
 
 public UserInfoBuilder(){
  //初始化rooElement和元组Element(userInfoElement)
  elementDecorator = new ElementDecorator();
  rootElement = new Element(UserInfo.ROOT_ELEMENT);
  userInfoElement = new Element(UserInfo.USER_INFO_ELEMENT);
 }
 
 /**
  * 创建,添加元素构建器
  * @param toggle 开关 [true 添加][false 创建]
  * @param userInfo 用户信息
  * @return element [toggle:true 返回主元素][toggle:false 返回根元素]
  */
 public Element addBuilder(boolean toggle,Object userInfo){
  if(userInfo instanceof UserInfoFacade){
   this.userInfo = (UserInfoFacade)userInfo;
  }else{
   if (logger.isDebugEnabled()) {logger.debug("addBuilder() - UserInfoFacade 对象匹配失败");}
  }
  //具体Construts动作省略,详情请看源码
  
  return toggle ? userInfoElement : rootElement.addContent(userInfoElement);
 }
 
 /**
  * 更新文档元素
  * @param userInfo 用户信息
  * @param mainElement 主元素
  */
 public void updateBuilder(Object userInfo,Element mainElement){
  
  if(userInfo instanceof UserInfoFacade){
   this.userInfo = (UserInfoFacade)userInfo;
  }else{
   if (logger.isDebugEnabled()) {logger.debug("updateBuilder() - UserInfoFacade 对象匹配失败");}
  }
  // 具体更新动作省略,详情请看源码
 }
}

2.2.3 BuilderFactory

public class BuilderFactory {
 private static final Logger logger = Logger.getLogger(BuilderFactory.class);
 
 public static Builder getBuilder(int builderType){

  switch(builderType){
   case Builder.USER_INFO_BUILDER :
    if (logger.isDebugEnabled()) {logger.debug("getBuilder() - 创建UserInfoBuilder实例完毕");}
    return new UserInfoBuilder();
   default:
    if (logger.isDebugEnabled()) {logger.debug("getBuilder() - 创建Builder实例失败");}
    return null;
  }
 }
}

这里用工厂来创建不同的Builder实例,起到ConcreteBuilder与XMLDAO解耦的效果。

2.3 通用XMLDAO类的设计
在这个类里一共有5个方法,它们将完成CRUD。(create、read、add、update、delete)
2.3.1 create方法
 /**
  * 创建单个文档
  * @param file 目标输出文档路径
  * @param facade elements外观
  * @param builderType 获得构建器的类型
  */
 public void create(String file,Object facade,int builderType){
  doc = new Document();
  // Construts Element
  doc.addContent(BuilderFactory.getBuilder(builderType).addBuilder(false,facade));
  // Output
  OutputFactory.newJDOMOutput().outputXML(file,doc);
 }

在这个方法里用BuilderFactory创建具体的UserInfoBuilder实例。

该模块类图:

此主题相关图片如下:
按此在新窗口浏览图片

待续...(3-22-2006)


[此贴子已经被作者于2006-3-22 21:05:35编辑过]

--  作者:SATOKO2006
--  发布时间:3/23/2006 11:23:00 AM

--  
嗯,不错,关注中。。。。。。
--  作者:malcolm
--  发布时间:3/23/2006 12:55:00 PM

--  
mark
--  作者:jomper
--  发布时间:3/23/2006 10:45:00 PM

--  
能不能让我 编辑一下原来的帖子,不然这样好郁闷.

2.3 通用XMLDAO类的设计
在这个类里一共有5个方法,它们将完成CRUD。(create、read、add、update、delete)
2.3.1 create方法
 /**
  * 创建单个文档
  * @param file 目标输出文档路径
  * @param facade elements外观
  * @param builderType 获得构建器的类型
  */
 public void create(String file,Object facade,int builderType){
  doc = new Document();
  // Construts Element
  doc.addContent(BuilderFactory.getBuilder(builderType).addBuilder(false,facade));
  // Output
  OutputFactory.newJDOMOutput().outputXML(file,doc);
 }

在这个方法里用BuilderFactory创建具体的UserInfoBuilder实例,实例类型由builderType决定。
然后根据facade完成具体的Construts Element动作,并将返回的element添加到Document对象。

2.3.2 add方法
 /**
  * 添加信息
  * @param file
  * @param userInfo
  * @param sc
  */
 public void add(String file,Object facade,int builderType,ServletContext sc){
  doc = (Document)InputFactory.newJDOMInput().inputXML(file,sc);

  doc.getRootElement().addContent(BuilderFactory.getBuilder(builderType).addBuilder(true,facade));
  OutputFactory.newJDOMOutput().outputXML(file,doc);

  if (logger.isDebugEnabled()) {logger.debug("add() - 数据添加完毕");}
 }

add方法跟create方法的唯一区别就是在Construts element之前,需要读取需要修改的内容。
至于ServletContext sc这个形式参数,大家不用理会,这个是用来从ParserPool获得XMLParser对象用的,直接设置为null就可以了,不会影响实际操作。


其他的几个方法我就不一一叙述了,实在大同小异。根据前面的分析,完全可以理解。详情请看源码吧。

2.4 facade对象到底是什么?
从我给出的类图可以看到UserInfoFacade类。(facade外观模式,详情请参考《Thinking in Patterns With Java》)它的作用实际是作为其他三个DTO的外界接口,用来简化与外界的操作。
细心的朋友还可能看到一个ElementDecorator类。(decorator装饰模式,详情请参考《Thinking in Patterns With Java》)这个是用来在ConcreteBuilder类里装饰ChildElement到元组Element。
当然如果你的XML文档并不复杂可能你根本不需要这样一个facade对象,这时你完全可以用简单的DTO来替代它。

3.0 适用范围
实际上想在这个基础上添加对其他XML文档,只需要基于Builder接口写出具体ConcreteBuilder类和facade类或者简单DTO就可以了。
所以它几乎是适用于绝大部分XML文档的。
另外input和output留出了接口,想换成DOM4J也不麻烦。
测试文件是 org.jomper.test.XMLDAOTest
我只是抛砖引玉,写这个文档的主要目的是希望能在大家的批评和意见下一起完善它,来简化所有XML爱好者编程。这是我的邮箱jomper.cn@gmail.com


4.0 参考文献
[1] Wes Biggs & Harry Evans,用 JDOM 简化 XML 编程. IBM Java Technology .
[2] Jason Hunter,JDOM Makes XML Easy.
[3] Bruce Eckle,Thinking in Patterns With Java. 电子工业出版社.
[4] Brett McLaughlin,Java Enterprise Best Practices.O'Reilly.


关键词:
元组元素: 元组元素相当于关系数据库里元组的概念,以一个元素来对应数据库里的一行数据。
DTO: Data Transform Object 数据传递对象。在《J2EE设计模式》(P139)已经建议用这个概念来替代VO(Value Object)。


下面是很多懒得看文档的人们喜欢的。
源码下载:


[此贴子已经被作者于2006-3-24 8:08:19编辑过]

--  作者:cong3232
--  发布时间:5/11/2006 10:12:00 AM

--  
楼主好像少了一个包的文件.在你提供的原代码中少了一个文件.


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
62.500ms