第18章 XMLマッピング

XMLマッピングはHibernate3.0では試験的な機能であり、非常に活動的に開発中です。

18.1. XMLデータでの作業

Hibernateでは永続性のPOJOを使って作業するのとほぼ同じようなやり方で、 永続性のXMLデータを使って作業できます。

HibernateはXMLツリーを操作するためのAPIとしてdom4jをサポートしています。 データベースからdom4jのツリーを復元するクエリを書くことができ、 ツリーに対して行った修正は自動的にデータベースと同期されます。 またXMLドキュメントを取得することができ、dom4jを使ってドキュメントをパースし、 Hibernateの任意の基本操作を使ってデータベースへ書き込むことができます。: つまり、persist(), saveOrUpdate(), merge(), delete(), replicate() 操作です(マージはまだサポートしていません)。

データのインポート/エクスポート、 JMSによるエンティティデータの外部化やSOAP、XSLTベースのレポートなど、 この機能には多くの用途があります。

単一のマッピングは、クラスのプロパティとXMLドキュメントのノードを 同時にデータベースへマッピングするために使うことができます。 またマッピングするクラスがなければ、 XMLだけをマッピングするために使うことができます。

18.1.1. XMLとクラスのマッピングを同時に指定する

これはPOJOとXMLを同時にマッピングする例です。:

<class name="Account" 
        table="ACCOUNTS" 
        node="account">
        
    <id name="accountId" 
            column="ACCOUNT_ID" 
            node="@id"/>
            
    <many-to-one name="customer" 
            column="CUSTOMER_ID" 
            node="customer/@id" 
            embed-xml="false"/>
            
    <property name="balance" 
            column="BALANCE" 
            node="balance"/>
            
    ...
    
</class>

18.1.2. XMLマッピングだけを指定する

これはPOJOクラスがないマッピングの例です。:

<class entity-name="Account" 
        table="ACCOUNTS" 
        node="account">
        
    <id name="id" 
            column="ACCOUNT_ID" 
            node="@id" 
            type="string"/>
            
    <many-to-one name="customerId" 
            column="CUSTOMER_ID" 
            node="customer/@id" 
            embed-xml="false" 
            entity-name="Customer"/>
            
    <property name="balance" 
            column="BALANCE" 
            node="balance" 
            type="big_decimal"/>
            
    ...
    
</class>

このマッピングにより、dom4jツリーか、 プロパティ名/値の組のグラフ(javaの Map)として データにアクセスできます。 プロパティの名前は、HQLクエリー内で参照できる純粋な論理構造です。

18.2. XMLマッピングのメタデータ

多くのHibernateのマッピング要素は node 属性が使用できます。 これによりXML属性の名前やプロパティやエンティティデータを保持する要素を指定できます。 node 属性のフォーマットは以下の中の1つでなければなりません。:

  • "element-name" - 指定したXML要素へマッピングします

  • "@attribute-name" - 指定したXML属性へマッピングします

  • "." - 親要素へマッピングします

  • "element-name/@attribute-name" - 指定したエレメントの指定した属性へマッピングします

コレクションと単一の値の関連に対して、 おまけの embed-xml 属性があります。 デフォルトの embed-xml="true" と設定した場合、 関連するエンティティ(値型のコレクション)のXMLツリーは、 直接関連を所有するエンティティのXMLツリー内に埋め込まれます。 反対に、embed-xml="false" と設定した場合、 参照される識別子の値だけが多重度1側の関連に対するXMLに現れ、 単純にコレクションはまったく現れなくなります。

あまりに多くの関連に対して embed-xml="true" としたままにするのは注意すべきです。 XMLは循環をうまく扱えません。

<class name="Customer" 
        table="CUSTOMER" 
        node="customer">
        
    <id name="id" 
            column="CUST_ID" 
            node="@id"/>
            
    <map name="accounts" 
            node="." 
            embed-xml="true">
        <key column="CUSTOMER_ID" 
                not-null="true"/>
        <map-key column="SHORT_DESC" 
                node="@short-desc" 
                type="string"/>
        <one-to-many entity-name="Account"
                embed-xml="false" 
                node="account"/>
    </map>
    
    <component name="name" 
            node="name">
        <property name="firstName" 
                node="first-name"/>
        <property name="initial" 
                node="initial"/>
        <property name="lastName" 
                node="last-name"/>
    </component>
    
    ...
    
</class>

この例では、実際のaccountのデータではなく、 accountのidのコレクションを埋め込むことにしました。 続きのHQLクエリです:

from Customer c left join fetch c.accounts where c.lastName like :lastName

このようなデータセットを返すでしょう

<customer id="123456789">
    <account short-desc="Savings">987632567</account>
    <account short-desc="Credit Card">985612323</account>
    <name>
        <first-name>Gavin</first-name>
        <initial>A</initial>
        <last-name>King</last-name>
    </name>
    ...
</customer>

<one-to-many> マッピングで embed-xml="true" と設定した場合、 データはこのようになるでしょう。

<customer id="123456789">
    <account id="987632567" short-desc="Savings">
        <customer id="123456789"/>
        <balance>100.29</balance>
    </account>
    <account id="985612323" short-desc="Credit Card">
        <customer id="123456789"/>
        <balance>-2370.34</balance>
    </account>
    <name>
        <first-name>Gavin</first-name>
        <initial>A</initial>
        <last-name>King</last-name>
    </name>
    ...
</customer>

18.3. XMLデータを扱う

XMLドキュメントを、アプリケーション内で再読み込みや更新をしてみましょう。 以下ではdom4jのセッションを取得することで行います。:

Document doc = ....;
       
Session session = factory.openSession();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
Transaction tx = session.beginTransaction();

List results = dom4jSession
    .createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName")
    .list();
for ( int i=0; i<results.size(); i++ ) {
    //add the customer data to the XML document
    Element customer = (Element) results.get(i);
    doc.add(customer);
}

tx.commit();
session.close();
Session session = factory.openSession();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
Transaction tx = session.beginTransaction();

Element cust = (Element) dom4jSession.get("Customer", customerId);
for ( int i=0; i<results.size(); i++ ) {
    Element customer = (Element) results.get(i);
    //change the customer name in the XML and database
    Element name = customer.element("name");
    name.element("first-name").setText(firstName);
    name.element("initial").setText(initial);
    name.element("last-name").setText(lastName);
}

tx.commit();
session.close();

XMLベースのデータのインポート/エクスポートを実装するために、 Hibernateの replicate() 操作をこの機能を結びつけるのは 極めて有効です。