283 lines
9.7 KiB
XML
283 lines
9.7 KiB
XML
|
<chapter id="xml">
|
||
|
<title>Mapeo XML</title>
|
||
|
|
||
|
<para><emphasis>
|
||
|
Nota que esta es una funcionalidad experimental en Hibernate 3.0 y está
|
||
|
bajo un desarrollo extremadamente activo.
|
||
|
</emphasis></para>
|
||
|
|
||
|
<sect1 id="xml-intro" revision="1">
|
||
|
<title>Trabajando con datos XML</title>
|
||
|
|
||
|
<para>
|
||
|
Hibernate te permite trabajar con datos XML persistentes en casi la misma forma
|
||
|
que trabajas con POJOs persistentes. Un árbol XML analizado (parsed) puede ser
|
||
|
pensado como sólo otra forma de representar los datos relacionales a nivel de objetos,
|
||
|
en vez de POJOs.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Hibernate soporta dom4j como API para manipular árboles XML. Puedes escribir
|
||
|
consultas que traigan árboles dom4j de la base de datos y tener cualquier modificación
|
||
|
que hagas al árbol sincronizada automáticamente a la base de datos. Puedes incluso tomar
|
||
|
un documento XML, analizarlo usando dom4j, y escribirlo a la base de datos con cualquiera
|
||
|
de las operaciones básicas de Hibernate: <literal>persist(), saveOrUpdate(), merge(),
|
||
|
delete(), replicate()</literal> (la fusión no está aún soportada).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Esta funcionalidad tiene muchas aplicaciones incluyendo la importación/exportación de datos,
|
||
|
externalización de datos de entidad vía JMS o SOAP y reportes basados en XSLT.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Un solo mapeo puede ser usado para mapear simultáneamente las propiedades de una clase y los nodos de un
|
||
|
documento XML a la base de datos, o, si no hay ninguna clase a mapear, puede ser usado para mapear sólo
|
||
|
el XML.
|
||
|
</para>
|
||
|
|
||
|
<sect2 id="xml-intro-mapping">
|
||
|
<title>Especificando los mapeos de XML y de clase juntos</title>
|
||
|
|
||
|
<para>
|
||
|
He aquí un ejemplo de mapear un POJO y XML simultáneamente:
|
||
|
</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>Especificando sólo un mapeo XML</title>
|
||
|
|
||
|
<para>
|
||
|
He aquí un ejemplo donde no hay ninguna clase POJO:
|
||
|
</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>
|
||
|
Este mapeo te permite acceder a los datos como un árbol dom4j, o como un grafo de pares nombre/valor de
|
||
|
propiedad (<literal>Map</literal>s de Java). Los nombres de propiedades son construcciones puramente
|
||
|
lógicas a las que se puede hacer referencia en consultas HQL.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="xml-mapping" revision="1">
|
||
|
<title>Mapeo de metadatos XML</title>
|
||
|
|
||
|
<para>
|
||
|
Muchos elementos de mapeo de Hibernate aceptan el atributo <literal>node</literal>. Esto te permite espcificar
|
||
|
el nombre de un atributo o elemento XML que contenga los datos de la propiedad o entidad. El formato del
|
||
|
atributo <literal>node</literal> debe ser uno de los siguientes:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para><literal>"element-name"</literal> - mapea al elemento XML mencionado</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para><literal>"@attribute-name"</literal> - mapea al atributo XML mencionado</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para><literal>"."</literal> - mapea al elemento padre</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<literal>"element-name/@attribute-name"</literal> -
|
||
|
mapea al atributo mencionado del elemento mencionado
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Para las colecciones y asociaciones monovaluadas, existe un atributo adicional <literal>embed-xml</literal>.
|
||
|
Si <literal>embed-xml="true"</literal>, que es el valor por defecto, el árbol XML para la entidad
|
||
|
asociada (o colección de tipo de valor) será embebida directamente en el árbol XML para la entidad que
|
||
|
posee la asociación. En otro caso, si <literal>embed-xml="false"</literal>, sólo el valor identificador
|
||
|
referenciado aparecerá en el XML para asociaciones de punto único y para las colecciones simplemente
|
||
|
no aparecerá en absoluto.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
¡Debes ser cuidadoso de no dejar <literal>embed-xml="true"</literal> para demasiadas asociaciones,
|
||
|
ya que XML no trata bien la circularidad!
|
||
|
</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>
|
||
|
en este caso, hemos decidido embeber la colección de ids de cuenta, pero no los datos reales de cuenta.
|
||
|
La siguiente consulta HQL:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[from Customer c left join fetch c.accounts where c.lastName like :lastName]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
devolvería conjuntos de datos como estos:
|
||
|
</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>
|
||
|
Si estableces <literal>embed-xml="true"</literal> en el mapeo <literal><one-to-many></literal>, los datos
|
||
|
podrían verse así:
|
||
|
</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>Manipulando datos XML</title>
|
||
|
|
||
|
<para>
|
||
|
Vamos a releer y actualizar documentos XML en la aplicación. Hacemos esto obteniendo una sesión dom4j:
|
||
|
</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>
|
||
|
Es extremadamente útil combinar esta funcionalidad con la operación <literal>replicate()</literal>
|
||
|
de Hibernate para implementar la importación/exportación de datos basada en XML.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
</chapter>
|
||
|
|