Hibernate.orgCommunity Documentation

Capítulo 14. Procesamiento por lotes

14.1. Inserciones de lotes
14.2. Actualizaciones de lotes
14.3. La interfaz de Sesión sin Estado
14.4. Operaciones de estilo DML

Un enfoque ingenuo para insertar 100.000 filas en la base de datos utilizando Hibernate puede verse así:

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();

Esto podría caer dentro de una OutOfMemoryException en algún sitio cerca de la fila 50.000. Esto se debe a que Hibernate tiene en caché todas las instancias de Customer recién insertadas en el caché de nivel de sesión. En este capítulo le vamos a mostrar cómo evitar este problema.

Si está realizando un procesamiento por lotes (batch processing), es necesario que habilite el uso del lote JDBC. Esto es esencial si quiere lograr un rendimiento óptimo. Establezca el tamaño de lote JDBC con un número razonable (por ejemplo, 10-50):

hibernate.jdbc.batch_size 20

Hibernate desactiva el lote de inserción a nivel de JDBC de forma transparente si usted utiliza un generador de identificador identiy.

También puede realizar este tipo de trabajo en un proceso en donde la interacción con el caché de segundo nivel se encuentre completamente desactivado:

hibernate.cache.use_second_level_cache false

Sin embargo, esto no es absolutamente necesario ya que podemos establecer explícitamente el CacheMode para descativar la interacción con el caché de segundo nivel.

Al hacer persistentes los objetos nuevos es necesario que realice flush() y luego clear() en la sesión regularmente para controlar el tamaño del caché de primer nivel.

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();

Para recuperar y actualizar datos se aplican las mismas ideas. Además, necesita utilizar scroll() para sacar ventaja de los cursores del lado del servidor en consultas que retornen muchas filas de datos.

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();

Opcionalmente, Hibernate proporciona una API orientada a comandos que se puede utilizar para datos que concurren desde y hacia la base de datos en forma de objetos separados. Un StatelessSession no tiene un contexto de persistencia asociado con él y no proporciona mucha de la semántica a un alto nivel de ciclo de vida. En particular, una sesión sin estado no implementa un caché en primer nivel y tampoco interactúa con cachés de segundo nivel o de peticiones. No implementa escritura-retrasada transaccional o chequeo de desactualizaciones automático. Las operaciones realizadas con la utilización de una sesión sin estado nunca usan cascadas para las instancias asociadas. La sesión sin estado ignora las colecciones. Las operaciones llevadas a cabo por una sesión sin estado ignoran el modelo de evento y los interceptores de Hibernte. Las sesiones sin estado son vulnerables a efectos de sobrenombamiento de datos debido a la falta de un caché de primer nivel. Una sesión sin estado es una abstracción en un nivel más bajo, mucho más cerca del JDBC subyacente.

StatelessSession session = sessionFactory.openStatelessSession();

Transaction tx = session.beginTransaction();
   
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    session.update(customer);
}
   
tx.commit();
session.close();

En este código de ejemplo, las instancias Customer retornadas por la petición se separan inmediatamente. Nunca se asocian con ningún contexto de persistencia.

Las operaciones insert(), update() y delete() definidas por la interfaz StatelessSession son consideradas como operaciones directas a nivel de filas de la base de datos. Esto resulta en una ejecución inmediata de un INSERT, UPDATE SQL o DELETE respectivamente. Tienen una semántica diferente a la de las operaciones save(), saveOrUpdate() y delete() definidas por la interfaz Session.

Como se discutió anteriormente, el mapeo objeto/relacional transparente se refiere a la administración del estado de objetos. El estado del objeto está disponible en la memoria. Esto significa que el manipular datos directamente en la base de datos (utilizando DML (del inglés Data Manipulation Language) las declaraciones: INSERT, UPDATE, DELETE) no afectarán el estado en la memoria. Sin embargo, Hibernate brinda métodos para la ejecución de declaraciones en masa DML del estilo de SQL, las cuales se realizan por medio del Lenguaje de Consulta de Hibernate (HQL).

La pseudo-sintáxis para las declaraciones UPDATE y DELETE es: ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?.

Algunos puntos a observar:

  • En la cláusula-from, la palabra clave FROM es opcional

  • Sólamente puede haber una entidad mencionada en la cláusula-from y puede tener un alias. Si el nombre de la entidad tiene un alias entonces cualquier referencia a la propiedad tiene que ser calificada utilizando ese alias. Si el nombre de la entidad no tiene un alias entonces es ilegal calificar cualquier referencia de la propiedad.

  • No se puede especificar ninguna unión ya sea implícita o explícita, en una consulta masiva de HQL. Se pueden utilizar subconsultas en la cláusula-where y en donde las subconsultas puedan contener uniones en sí mismas.

  • La cláusula-where también es opcional.

Como ejemplo, para ejecutar un UPDATE de HQL, utilice el método Query.executeUpdate(). El método es nombrado para aquellos familiarizados con el PreparedStatement.executeUpdate() de JDBC:

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or 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();

Para mantenerse de acuerdo con la especificación de EJB3, las declaraciones UPDATE de HQL, por defecto no afectan la versión o los valores de la propiedad sello de fecha para las entidades afectadas. Sin embargo, puede obligar a Hibernate a poner en cero apropiadamente los valores de las propiedades versión o sello de fecha por medio de la utilización de una actualización con versión. Esto se logra agregando la palabra clave VERSIONED después de la palabra clave UPDATE.

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();

Observe que los tipos de versiones personalizados (org.hibernate.usertype.UserVersionType) no están permitidos en conjunto con una declaración update versioned.

Para ejecutar un DELETE HQL, utilice el mismo método Query.executeUpdate():

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();

El valor int retornado por el método Query.executeUpdate() indica el número de entidades afectadas por la operación. Considere que esto puede estar correlacionado o no con el número de filas afectadas en la base de datos. Una operación masiva de HQL puede llegar a causar que se ejecuten múltiples declaraciones SQL reales, por ejemplo, para una subclase-joined. El número retornado indica el número de entidades realmente afectadas por la declaración. De vuelta al ejemplo de la subclase joined, un borrado contra una de las subclases puede resultar, de hecho, en borrados de no sólamente la tabla a la cual esa subclase esta mapeada, sino también la tabla "raíz" y potencialmente las tablas de subclases joined hasta la jerarquía de herencia.

La pseudo-sintáxis para las declaraciones INSERT es: INSERT INTO EntityName properties_list select_statement. Algunos puntos que se deben observar son:

  • Sólamente se soporta la forma INSERT INTO ... SELECT ..., no la forma INSERT INTO ... VALUES ...

    La lista de propiedades (properties_list) es análoga a la column speficiation en la declaración INSERT de SQL. Para las entidades involucradas en la herencia mapeada, sólamente las propiedades definidas directamente en ese nivel de clase dado se pueden utlizar en la lista de propiedades. Las propiedades de la superclase no están permitidas, y las propiedaeds de la subclase no tienen sentido. Es decir, las declaraciones INSERT son inherentemente no-polimórficas.

  • select_statement puede ser cualquier consulta select de HQL válida con la advertencia de que los tipos de retorno coincidan con los tipos esperados por el insert. Actualmente, esto se verifica durante la compilación de la consulta en vez de permitir que se relegue la verificación a la base de datos. Sin embargo, esto puede crear problemas entre los Types de Hibernate, los cuales son equivalentes y no iguales. Esto puede crear problemas con las uniones mal hechas entre una propiedad definida como un org.hibernate.type.DateType y una propiedad definida como una org.hibernate.type.TimestampType, aunque puede que la base de datos no distinga o no pueda manejar la conversión.

  • Para la propiedad id, la declaración insert le da dos opciones. Puede especificar explícitamente la propiedad id en la lista de propiedades (properties_list ) (en tal caso su valor se toma de la expresión de selección correspondiente) o se omite de la lista de propiedades (en tal caso se utiliza un valor generado). Esta última opción sólamente está disponible cuando se utilizan generadores de id que operan en la base de datos, intentando utilizar esta opción con cualquier generador de tipo "en memoria" provocará una excepción durante el análisis sintáctico. Note que para los propósitos de esta discusión, los generadores en la base de datos son considerados org.hibernate.id.SequenceGenerator (y sus subclases) y cualquier implementador de org.hibernate.id.PostInsertIdentifierGenerator. La excepción más importante aquí es org.hibernate.id.TableHiLoGenerator, la cual no se puede utilizar ya que no expone una manera selectiva de obtener sus valores.

  • Para las propiedades mapeadas como version o timestamp, la declaración insert le da dos opciones. Puede especificar la propiedad en la lista de propiedades (en tal caso su valor se toma de las expresiones de selección correspondientes) o se omite de la lista de propiedades (en tal caso se utiliza el seed value definido por el org.hibernate.type.VersionType).

Un ejemplo de la ejecución de la declaración INSERT de HQL:

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.createQuery( hqlInsert )
        .executeUpdate();
tx.commit();
session.close();