hibernate-orm/reference/en/modules/xml.xml

287 lines
9.4 KiB
XML
Executable File

<chapter id="xml">
<title>XML Mapping</title>
<para><emphasis>
Note that this is an experimental feature in Hibernate 3.0 and is under
extremely active development.
</emphasis></para>
<sect1 id="xml-intro" revision="1">
<title>Working with XML data</title>
<para>
Hibernate lets you work with persistent XML data in much the same way
you work with persistent POJOs. A parsed XML tree can be thought of
as just another way to represent the relational data at the object level,
instead of POJOs.
</para>
<para>
Hibernate supports dom4j as API for manipulating XML trees. You can write
queries that retrieve dom4j trees from the database and have any
modification you make to the tree automatically synchronized to the
database. You can even take an XML document, parse it using dom4j, and
write it to the database with any of Hibernate's basic operations:
<literal>persist(), saveOrUpdate(), merge(), delete(), replicate()</literal>
(merging is not yet supported).
</para>
<para>
This feature has many applications including data import/export,
externalization of entity data via JMS or SOAP and XSLT-based reporting.
</para>
<para>
A single mapping may be used to simultaneously map properties of a class
and nodes of an XML document to the database, or, if there is no class to map,
it may be used to map just the XML.
</para>
<sect2 id="xml-intro-mapping">
<title>Specifying XML and class mapping together</title>
<para>
Here is an example of mapping a POJO and XML simultaneously:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
</sect2>
<sect2 id="xml-onlyxml">
<title>Specifying only an XML mapping</title>
<para>
Here is an example where there is no POJO class:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
This mapping allows you to access the data as a dom4j tree, or as a graph of
property name/value pairs (java <literal>Map</literal>s). The property names
are purely logical constructs that may be referred to in HQL queries.
</para>
</sect2>
</sect1>
<sect1 id="xml-mapping" revision="1">
<title>XML mapping metadata</title>
<para>
Many Hibernate mapping elements accept the <literal>node</literal> attribute.
This let's you specify the name of an XML attribute or element that holds the
property or entity data. The format of the <literal>node</literal> attribute
must be one of the following:
</para>
<itemizedlist spacing="compact">
<listitem>
<para><literal>"element-name"</literal> - map to the named XML element</para>
</listitem>
<listitem>
<para><literal>"@attribute-name"</literal> - map to the named XML attribute</para>
</listitem>
<listitem>
<para><literal>"."</literal> - map to the parent element</para>
</listitem>
<listitem>
<para>
<literal>"element-name/@attribute-name"</literal> -
map to the named attribute of the named element
</para>
</listitem>
</itemizedlist>
<para>
For collections and single valued associations, there is an additional
<literal>embed-xml</literal> attribute. If <literal>embed-xml="true"</literal>,
the default, the XML tree for the associated entity (or collection of value type)
will be embedded directly in the XML tree for the entity that owns the association.
Otherwise, if <literal>embed-xml="false"</literal>, then only the referenced
identifier value will appear in the XML for single point associations and
collections will simply not appear at all.
</para>
<para>
You should be careful not to leave <literal>embed-xml="true"</literal> for
too many associations, since XML does not deal well with circularity!
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
in this case, we have decided to embed the collection of account ids, but not
the actual account data. The following HQL query:
</para>
<programlisting><![CDATA[from Customer c left join fetch c.accounts where c.lastName like :lastName]]></programlisting>
<para>
Would return datasets such as this:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
If you set <literal>embed-xml="true"</literal> on the <literal>&lt;one-to-many&gt;</literal>
mapping, the data might look more like this:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
</sect1>
<sect1 id="xml-manipulation" revision="1">
<title>Manipulating XML data</title>
<para>
Let's rearead and update XML documents in the application. We do this by
obtaining a dom4j session:
</para>
<programlisting><![CDATA[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();]]></programlisting>
<programlisting><![CDATA[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();]]></programlisting>
<para>
It is extremely useful to combine this feature with Hibernate's <literal>replicate()</literal>
operation to implement XML-based data import/export.
</para>
</sect1>
</chapter>