程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 將XML結點轉換成JAVABEAN並存入數據庫

將XML結點轉換成JAVABEAN並存入數據庫

編輯:關於JAVA

1.概述

我們要將外部系統給的XML文件進行解析,並存入到數據庫。

但是我們並沒有DTD或者Schema,只有一個WORD格式的說明文檔;更離譜的是,XML結點樹的結構(即XML結點與XML結點之間的關系)與業務Bean樹的結構(即業務Bean與業務Bean的關系)並不完全一致,比如說,從業務角度講,一只豬有只豬頭,而在XML裡,卻寫成了 pig --content --pighead 的三級關系,無端端多了一個content結點! 沒有DTD/Schema,結構又不規范,我們就沒法用自動化的第三方JAVA轉換API進行解析,而只能手動地、一個一個地解析。但在手動解析的過程中,我們仍然發現各個結點的解析和入庫中有很多東西是共同的,或者有共同的規律,這些東西可以抽出來作為一個准框架,然後再將結點中不同的部分開放出來,允許具體的結點做具體的實現,並最終形成一個半自動的解析/入庫框架。

為什麼說它是半自動的?它有哪些限制?

自動:不必為每個結點編寫XML 解析代碼和入庫代碼

“半”:需手動地編寫每個JAVABEAN,並手動地為每個BEAN建表

限制:

a.所有業務字段的類型只能設為STRING/VARCHAR,並且非業務字段的類型在BEAN中不能為STRING

b.BEAN名與表名必須相同,或者可以進行一對一映射

c.BEAN的成員變量名必須與XML結點的屬性名/元素名相同,或者可以進行一對一映射

這三種限制都是利用JAVA反射機制進行自動操作的前提。

2.基本思想

所謂的XML解析,就是將XML結點轉換成JAVABEAN實例,XML結點的ATTRIBUTE值和ELEMENT值就是JAVABEAN實例的成員變量值; 所謂的持久化,就是將JAVABEAN實例變成數據庫對應表中的一條記錄,JAVABEAN實例的成員變量值就是記錄中某個字段的值,或者其他表中某個參考了該記錄的另一條記錄。

而在XML中,JAVABEAN體系中,數據據表關系結構中,結點和結點之間的關系都是樹形的關系。整體的解析和入庫,就是在遍歷樹時執行轉換動作。而我們知道,樹的遍歷是可以用遞歸算法實現的,而遞歸,就不用說了吧,它是實現程序“自動化”的主要途徑之一。

以下是對各“樹”的具體分析:

假設兩個業務實體A和B之間存在聚合關系(父子關系)。那麼具體可分三種情況:

a.B是一個原子字段(即不可再分),並且是A的一個屬性。

XML中,B是A的XML ATTRIBUTE或者A的原子ELEMENT

BEAN中,B是A的成員變量,並且B是一個JAVA內置的數據類型

數據庫中,B是A表的一個列

b.B是一個復合字段,並且是A的一個屬性,而且和A是1:1關系

XML中,B是A的ELEMENT,並且B有自己的ELEMENT或者ATTRIBUTE

BEAN中,B是A的成員變量,並且程序中有個B類

數據庫中,B表是A表的子表(即B外鍵參考了A表)

c.B是一個復合字段,並且是A的一個屬性,而且和A是N:1關系

XML中,B是A的ELEMENT,並且B有自己的ELEMENT或者ATTRIBUTE

BEAN中,B組成一個類集(List,Set)共同作為A的成員變量,並且程序中有個B類

數據庫中,B表是A表的子表(即B外鍵參考了A表)

了解了這三種情況,接下來就好辦了。程序每抓到一個結點,都要遞歸地進行以下處理:先處理它的原子屬性(情形a),接著處理它的單個子結點(情形b),最後處理它的類集子結點( 情形c)。

3.代碼實現的重點

兩個重點:

a.如何讓業務實體在三棵樹內一一對應好?

b.如何發現樹形關系,比如A的屬性有哪些,A的子結點有哪些?

問題a很簡單,就是讓三棵樹裡相同的業務實體取相同的名字。

a.解析XML時發現 結點X 的 屬性Y 等於 值Z,則執行PropertyUtils.setProperty(結點X , 屬性Y , 值Z)即可。在這裡X,Y,Z是變量,程序不用關心具體的結點和屬性是哪些個。需要注意的是,如果屬性Y是原子字段,則要求屬性Y必須為String類型,否則程序不知道將值Z轉換成哪種類型(注:關於PropertyUtils, 請見apache commons Beanutils )

b.入庫時發現x.getY()=z。如果屬性y是原子字段,則執行SQL insert into X(...,y,...) values (...,z,...),這裡要求y字段必須為varchar/char類型, 以免發生類型轉換錯誤.

關於問題b

XML樹:JDOM, dom4j等都可以直接找到父子關系

BEAN體系:

I.原子屬性。我們限定一個BEAN中所有有業務意義的原子字段的類型都STRING,所有String類型的字段都是業務字段

II.單個子結點。我們讓所有有業務意義的非原子字段都實現一個共同的接口BusiNode,這樣一個BEAN中所有BusiNode成員都是這個BEAN的子結點

III.類集子結點。我們也可以限定所有且只有類集子結點可以使用List或Set類型,這樣可以利用過濾出所有類集子結點。然而,在JAVA1.4及以前的版本裡,程序並不知道過濾出的類集子結點是哪個Class的實例(因為沒有泛型),也就沒辦法實例化一個類集子結點(見後文),因此只能手動注冊類集子結點的屬性名和Class。JAVA1.5以上的版本我沒用過,不知道可不可以解決這個問題。

數據庫表關系: 這就不用多說了,就是通過外鍵參考。因此每類結點對應的表中,都必須有個外鍵,以參考它的父結點;還必須有個主鍵,以供它的子結點參考。各表的外鍵名必須相同並為一常數,否則程序生成INSERT SQL時才可以不用理會具體表的具體的外鍵名。

程序在解析時,遍歷的是BEAN樹;在持久化時也是。比起XML樹,BEAN樹代表真正的業務結構;比起數據庫表關系樹,BEAN樹才能由父至子地進行先序遍歷

4.其他問題

a.要讓程序知道,原子屬性中哪些是XML結點的屬性,哪些是XML結點的原子ELEMENT。代碼中這是兩個抽象方法,必須讓具體的結點類實現

b.回顧本文概述部分提到的“pig --content --pighead 的三級關系,無端端多了一個content結點”,因此我們要讓程序知道,pighead,pigfoot等結點的子結點,究竟是pig,還是pig下的content。處理不規范XML時要注意這個問題。這也是一個抽象方法,必須讓具體的結點類實現

c.與上一條類似但更變態的,是類集結點的不規范問題。假設一個pig有多個pighead,那結構可能為 pig--pighead,pighead,...,也可能為pig--pigheads--content,content.... 必須讓程序知道某個具體結點用的是哪種模式

5.代碼

核心:多態 + 遞歸

a.接口BusiNode

import java.util.*;
import org.dom4j.Element;
/**
* 每個結點都要實現的接口
* 它提供了一些方法以方便實現遞歸的XML解析和持久化
*
*/
public interface BusiNode {
  
  /**
   * 所有類型為不可分類型的屬性
   * @return 屬性名的集合
   */
  public List getAtomicPropNames();
  
  /**
   * 一些成員變量。這些成員變量是XML結點的屬性
   * @return
   */
  public List getXmlAttributes();
  
  /**
   * 一些成員變量。這些成員變量是XML結點的子元素,並且類型為不可分的
   * @return
   */
  public List getXmlAtomicElements();
  
  
  /**
   * 所有類型為類集的屬性,並且這些類集中每個元素的類型都是BusiNode
   * @return key = 屬性名, value = 屬性類的Class對象。
   * 如果為空不返回NULL,而是空的MAP
   */
  public Map getCollectionPropsMap();
  
  /**
   * 所有類型為BusiNode的屬性
   * @return 屬性名的集合
   */
  public List getBusiNodePropNames();
  
  
  /**
   * 從XML中解析出來。
   * @param element
   * @return
   */
  public void parseFromXML(Element element);  
  }

b.默認的實現

import java.lang.reflect.Field;
import java.util.*;
import org.apache.commons.beanutils.PropertyUtils;
import org.dom4j.Attribute;
import org.dom4j.Element;
/**
* 默認的BUSI NODE。 繼承此類的BUSI NODE 需滿足 所有不可分屬性集=String類型的屬性集
* MyUtils類的代碼欠奉
*
*/
public abstract class DefaultBusiNode implements BusiNode {
  public List getAtomicPropNames() {
    return MyUtils.getFieldNamesOfClass(this.getClass(), String.class);
  }
  public List getBusiNodePropNames() {
    return MyUtils.getFieldNamesOfClass(this.getClass(), BusiNode.class);
  }
  /*
   * 所有子元素的父元素。有時是本結點,有時是本結點下的元素。變態
   */
  public abstract Element getXmlElementParent(Element rootElement);
  /*
   * 類集子結點根元素的Iterator 。 假設一個pig有多個pighead,
   * 那結構可能為 pig--pighead,pighead,...,
   * 也可能為pig--pigheads--content,content....
   * 必須讓程序知道某個具體結點用的是哪種模式
   *
 
   * 如果為空則返回一個空類集的Iterator ,不要返回NULL
   */
  public abstract Iterator getCollectionElementIterator(
      Element xmlElementParent, String attName);
  /**
   * 解析XML屬性
   *
   * @param rootElement
   */
  protected void parseAttributesFromXml(Element rootElement) {
    List xmlAttributes = this.getXmlAttributes();
    for (int i = 0; i < this.getXmlAttributes().size(); i++) {
      String attName = (String) xmlAttributes.get(i);
      Attribute att = rootElement.attribute(attName);
      if (att != null) {
        try {
          PropertyUtils.setProperty(this, attName, att.getValue());
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    }
  }
  /**
   * 解析不可分的Element
   *
   * @param rootElement
   */
  protected void parseAtomicElementFromXml(Element rootElement) {
    Element xmlElementParent = getXmlElementParent(rootElement);
    if (xmlElementParent == null) {
      return;
    }
    List xmlElements = this.getXmlAtomicElements();
    for (int i = 0; i < xmlElements.size(); i++) {
      String attName = (String) xmlElements.get(i);
      Element elmt = xmlElementParent.element(attName);
      if (elmt != null) {
        try {
          PropertyUtils.setProperty(this, attName, elmt.getText());
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    }
  }
  /**
   * 解析BusiNode屬性
   *
   * @param rootElement
   */
  protected void parseBusiNodeElementFromXml(Element rootElement) {
    Element xmlElementParent = getXmlElementParent(rootElement);
    if (xmlElementParent == null) {
      return;
    }
    // 再解析BusiNode屬性
    List busiNodePropNames = this.getBusiNodePropNames();
    for (int i = 0; i < busiNodePropNames.size(); i++) {
      try {
        String attName = (String) busiNodePropNames.get(i);
        Element elmt = xmlElementParent.element(attName);
        if (elmt != null) {
          Field field = this.getClass().getDeclaredField(attName);
          BusiNode att = (BusiNode) field.getType().newInstance();
          att.parseFromXML(elmt);
          PropertyUtils.setProperty(this, attName, att);
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }
  /**
   * 解析類集屬性
   *
   * @param rootElement
   */
  protected void parseCollectionPropsFromXml(Element rootElement) {
    // 先解析XML屬性
    Element xmlElementParent = getXmlElementParent(rootElement);
    if (xmlElementParent == null) {
      return;
    }
    // 最後解析類集屬性
    Map collectionPropsMap = this.getCollectionPropsMap();
    for (Iterator it = collectionPropsMap.keySet().iterator(); it.hasNext();) {
      try {
        String attName = (String) it.next();
        Collection coll = (Collection) PropertyUtils.getProperty(this,
            attName);
        Class attType = (Class) collectionPropsMap.get(attName);
        Iterator collElementsIt = this.getCollectionElementIterator(
            xmlElementParent, attName);
        // xmlElementParent.elementIterator(attName);
        while (collElementsIt.hasNext()) {
          Element collElmt = (Element) collElementsIt.next();
          BusiNode sinlgeAtt = (BusiNode) attType.newInstance();
          sinlgeAtt.parseFromXML(collElmt);
          coll.add(sinlgeAtt);
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }
  /**
   * 從XML中解析出結點。此方法可能拋出RumtimeException
   */
  public void parseFromXML(Element rootElement) {
    
    this.parseAttributesFromXml(rootElement);
    this.parseAtomicElementFromXml(rootElement);
    this.parseBusiNodeElementFromXml(rootElement);
    this.parseCollectionPropsFromXml(rootElement);
  }
}
/**
* 入庫
* JdbcUtil,MyUtils的代碼欠奉
*
*/
public class BusiNodeDAO {
  
  private Long saveBusiNode(BusiNode node, Long parentNodeId) {
    // 先存儲原子屬性
    Long id = saveBareBusiNode(node, parentNodeId);
    // 再存儲類集屬性
    Map collectionPropsMap = node.getCollectionPropsMap();
    for (Iterator it = collectionPropsMap.keySet().iterator(); it.hasNext();) {
      String attName = (String) it.next();
      Collection coll = null;
      try {
        coll = (Collection) PropertyUtils.getProperty(node, attName);
      } catch (Exception e) {        
        throw new RuntimeException("編碼錯誤");
      }
      for (Iterator iitt = coll.iterator(); iitt.hasNext();) {
        BusiNode subNode = (BusiNode) iitt.next();
        saveBusiNode(subNode, id);
      }
    }
    // 最後存儲所有BusiNode屬性
    Iterator iitt = node.getBusiNodePropNames().iterator();
    while (iitt.hasNext()) {
      BusiNode subNode = null;
      try {
        subNode = (BusiNode) PropertyUtils.getProperty(node,
            (String) iitt.next());
      } catch (Exception e) {        
        throw new RuntimeException("編碼錯誤");
      }
      if (subNode != null) {
        saveBusiNode(subNode, id);
      }
    }
    return id;
  }
  /**
   * 插入某個BusiNode的根結點,此方法可能拋出RuntimeException
   *
   * @param node
   * @return
   */
  private Long saveBareBusiNode(BusiNode node, Long parentNodeId) {
    StringBuffer sbForSql = new StringBuffer();
    List paramValues = new ArrayList();
    genInsertSqlAndParam(node, parentNodeId, node.getAtomicPropNames(),
        sbForSql, paramValues);
    return new Long(JdbcUtil.queryForLong(
        sbForSql.toString(), paramValues.toArray()));
  }
  /**
   * 生成某個結點的插入語句和paramValues數組,此方法可能拋出RuntimeException
   *
   * @param node
   * @param columnNames
   * @param sbForSql
   * @param paramValues
   */
  private void genInsertSqlAndParam(BusiNode node, Long parentNodeId,
      List columnNames, StringBuffer sbForSql, List paramValues) {
    sbForSql.append(" insert into ");
    sbForSql.append(MyUtils.getClassBareName(node.getClass()));
    List cns = new ArrayList();
    cns.addAll(columnNames);
    cns.add("parentNodeId");
    sbForSql.append(MyUtils.encloseWithCurve(MyUtils
        .joinCollectionStrings(cns, ",")));
    sbForSql.append(" values ");
    List qms = new ArrayList(); // 問號
    for (Iterator it = columnNames.iterator(); it.hasNext();) {
      qms.add("?");
      String cn = (String) it.next();
      try {
        paramValues.add(PropertyUtils.getProperty(node, cn));
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
    qms.add("?"); // parentNodeId
    paramValues.add(parentNodeId);
    sbForSql.append(MyUtils.encloseWithCurve(MyUtil
        .joinCollectionStrings(qms, ",")));
    sbForSql.append(";select @@identity");
  }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved