193 lines
7.8 KiB
XML
193 lines
7.8 KiB
XML
<chapter id="batch">
|
|
<title>Procesamiento por lotes</title>
|
|
|
|
<para>
|
|
Un enfoque ingenuo para insertar 100.000 filas en la base de datos usando Hibernate podría verse así:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
|
Transaction tx = session.beginTransaction();
|
|
for ( int i=0; i<100000; i++ ) {
|
|
Customer customer = new Customer(.....);
|
|
session.save(customer);
|
|
}
|
|
tx.commit();
|
|
session.close();]]></programlisting>
|
|
|
|
<para>
|
|
Esto podría caer sobre una <literal>OutOfMemoryException</literal> en algún sitio
|
|
cerca de la fila 50.000. Esto es porque Hibernate tiene en caché todas las instancias
|
|
de <literal>Customer</literal> recién instanciadas en el caché de nivel de sesión.
|
|
</para>
|
|
|
|
<para>
|
|
En este capítulo te mostraremos cómo evitar este problema. Primero, sin embargo,
|
|
si estás haciendo procesamiento por lotes (batch processing), es absolutamente crítico
|
|
que habilites el uso de loteo JDBC, si pretendes lograr un rendimiento razonable.
|
|
Establece el tamaño de lote JDBC a un número razonable (digamos 10-50):
|
|
</para>
|
|
|
|
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
|
|
|
|
<para>
|
|
Podrías además querer hacer este tipo de trabajo en un proceso donde la interacción con el caché de
|
|
segundo nivel esté completamente deshabilitado:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
|
|
|
|
<sect1 id="batch-inserts">
|
|
<title>Inserciones en lote</title>
|
|
|
|
<para>
|
|
Al hacer persistentes objetos nuevos, debes limpiar con <literal>flush()</literal> y
|
|
llamar a <literal>clear()</literal> en la sesión regularmente, para controlar el tamaño
|
|
del caché de primer nivel.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
for ( int i=0; i<100000; i++ ) {
|
|
Customer customer = new Customer(.....);
|
|
session.save(customer);
|
|
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
|
|
//flush a batch of inserts and release memory:
|
|
session.flush();
|
|
session.clear();
|
|
}
|
|
}
|
|
|
|
tx.commit();
|
|
session.close();]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="batch-update" >
|
|
<title>Actualizaciones en lote</title>
|
|
|
|
<para>
|
|
Para recuperar y actualizar datos se aplican las mismas ideas. Adicionalmente, necesitas usar
|
|
<literal>scroll()</literal> para sacar ventaja de los cursores del lado del servidor en consultas
|
|
que devuelvan muchas filas de datos.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
ScrollableResults customers = session.getNamedQuery("GetCustomers")
|
|
.setCacheMode(CacheMode.IGNORE)
|
|
.scroll(ScrollMode.FORWARD_ONLY);
|
|
int count=0;
|
|
while ( customers.next() ) {
|
|
Customer customer = (Customer) customers.get(0);
|
|
customer.updateStuff(...);
|
|
if ( ++count % 20 == 0 ) {
|
|
//flush a batch of updates and release memory:
|
|
session.flush();
|
|
session.clear();
|
|
}
|
|
}
|
|
|
|
tx.commit();
|
|
session.close();]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="batch-direct">
|
|
<title>update/delete en masa</title>
|
|
|
|
<para>
|
|
Como ya se ha discutido, el mapeo objeto/relacional automático y transparente se refiere
|
|
al manejo de estado de objetos. Esto implica que el estado del objeto está disponible
|
|
en memoria, por lo tanto actualizar o borrar (usando <literal>UPDATE</literal> y
|
|
<literal>DELETE</literal> de SQL) datos directamente en la base de datos no afectará el
|
|
estado en memoria. Sin embargo, Hibernate provee métodos para la ejecución de sentencias
|
|
del estilo de <literal>UPDATE</literal> y <literal>DELETE</literal> de SQL que se realizan
|
|
a través del Lenguaje de Consulta de Hibernate (Hibernate Query Language o
|
|
<xref linkend="queryhql">HQL</xref>).
|
|
</para>
|
|
|
|
<para>
|
|
La pseudo-sintáxis para sentencias <literal>UPDATE</literal> y <literal>DELETE</literal> es:
|
|
<literal>( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)?</literal>. Algunos puntos
|
|
a tener en cuenta:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
En la cláusula-from, la palabra clave FROM es opcional
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Puede haber sólo una clase mencionada en la cláusula-from, y <emphasis>no puede</emphasis>
|
|
tener un alias.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
No puede especificarse ningún join (bien implícito o explícito) en una consulta masiva de HQL.
|
|
Pueden usarse subconsultas en la cláusula-where.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
La cláusula-where es también opcional.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Como un ejemplo, para ejecutar un <literal>UPDATE</literal> HQL, usa el
|
|
método <literal>Query.executeUpdate()</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
String hqlUpdate = "update Customer set name = :newName where name = :oldName";
|
|
int updatedEntities = s.createQuery( hqlUpdate )
|
|
.setString( "newName", newName )
|
|
.setString( "oldName", oldName )
|
|
.executeUpdate();
|
|
tx.commit();
|
|
session.close();]]></programlisting>
|
|
|
|
<para>
|
|
Para ejecutar un <literal>DELETE</literal> HQL, usa el mismo método <literal>Query.executeUpdate()</literal>
|
|
(el método está nombrado para aquellos familiarizados con
|
|
<literal>PreparedStatement.executeUpdate()</literal> de JDBC):
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
String hqlDelete = "delete Customer where name = :oldName";
|
|
int deletedEntities = s.createQuery( hqlDelete )
|
|
.setString( "oldName", oldName )
|
|
.executeUpdate();
|
|
tx.commit();
|
|
session.close();]]></programlisting>
|
|
|
|
<para>
|
|
El valor <literal>int</literal> devuelto por el método <literal>Query.executeUpdate()</literal>
|
|
indica el número de entidades afectadas por la operación. Considera que esto puede o no
|
|
correlacionarse al número de filas afectadas en la base de datos. Una operación masiva HQL podría
|
|
resultar en que se ejecuten múltiples sentencias de SQL reales, para joined-subclass, por ejemplo.
|
|
El número devuelto indica el número de entidades reales afectadas por la sentencia. Volviendo al
|
|
ejemplo de joined-subclass, un borrado contra una de las subclases puede resultar realmente en
|
|
borrados contra no sólo la tabla a la que está mapeada esa subclase, sino también la tabla "raíz"
|
|
y potencialmente tablas de joined-subclass más debajo en la jerarquía de herencia.
|
|
</para>
|
|
|
|
<para>
|
|
Ten en cuenta que existen actualmente unas pocas limitaciones con las operaciones HQL masivas,
|
|
que serán atendidas en lanzamientos futuros; consulta la hoja de ruta de JIRA para más detalles.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|