Hibernate: procesamiento por lotes

Considere una situación en la que necesita cargar una gran cantidad de registros en su base de datos usando Hibernate. A continuación se muestra el fragmento de código para lograr esto usando Hibernate:

Session session = SessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
   Employee employee = new Employee(.....);
   session.save(employee);
}
tx.commit();
session.close();

De forma predeterminada, Hibernate almacenará en caché todos los objetos persistentes en el caché de nivel de sesión y, en última instancia, su aplicación se caería con un OutOfMemoryExceptionen algún lugar alrededor de la fila 50.000. Puede resolver este problema, si está utilizandobatch processing con Hibernate.

Para utilizar la función de procesamiento por lotes, primero configure hibernate.jdbc.batch_sizecomo tamaño de lote a un número de 20 o 50 dependiendo del tamaño del objeto. Esto le dirá al contenedor de hibernación que cada X filas se insertarán como lote. Para implementar esto en su código, necesitaríamos hacer pequeñas modificaciones de la siguiente manera:

Session session = SessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
   Employee employee = new Employee(.....);
   session.save(employee);
   if( i % 50 == 0 ) { // Same as the JDBC batch size
      //flush a batch of inserts and release memory:
      session.flush();
      session.clear();
   }
}
tx.commit();
session.close();

El código anterior funcionará bien para la operación INSERT, pero si está dispuesto a realizar la operación ACTUALIZAR, puede lograrlo usando el siguiente código:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

ScrollableResults employeeCursor = session.createQuery("FROM EMPLOYEE").scroll();
int count = 0;

while ( employeeCursor.next() ) {
   Employee employee = (Employee) employeeCursor.get(0);
   employee.updateEmployee();
   seession.update(employee); 
   if ( ++count % 50 == 0 ) {
      session.flush();
      session.clear();
   }
}
tx.commit();
session.close();

Ejemplo de procesamiento por lotes

Modifiquemos el archivo de configuración para agregar hibernate.jdbc.batch_size propiedad -

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM 
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
   <session-factory>
   
      <property name = "hibernate.dialect">
         org.hibernate.dialect.MySQLDialect
      </property>
   
      <property name = "hibernate.connection.driver_class">
         com.mysql.jdbc.Driver
      </property>

      <!-- Assume students is the database name -->
   
      <property name = "hibernate.connection.url">
         jdbc:mysql://localhost/test
      </property>
   
      <property name = "hibernate.connection.username">
         root
      </property>
   
      <property name = "hibernate.connection.password">
         root123
      </property>
   
      <property name = "hibernate.jdbc.batch_size">
         50
      </property>

      <!-- List of XML mapping files -->
      <mapping resource = "Employee.hbm.xml"/>

   </session-factory>
</hibernate-configuration>

Considere la siguiente clase de empleado de POJO:

public class Employee {
   private int id;
   private String firstName; 
   private String lastName;   
   private int salary;  

   public Employee() {}
   
   public Employee(String fname, String lname, int salary) {
      this.firstName = fname;
      this.lastName = lname;
      this.salary = salary;
   }
   
   public int getId() {
      return id;
   }
   
   public void setId( int id ) {
      this.id = id;
   }
   
   public String getFirstName() {
      return firstName;
   }
   
   public void setFirstName( String first_name ) {
      this.firstName = first_name;
   }
   
   public String getLastName() {
      return lastName;
   }
   
   public void setLastName( String last_name ) {
      this.lastName = last_name;
   }
   
   public int getSalary() {
      return salary;
   }
   
   public void setSalary( int salary ) {
      this.salary = salary;
   }
}

Creemos la siguiente tabla EMPLOYEE para almacenar los objetos Employee:

create table EMPLOYEE (
   id INT NOT NULL auto_increment,
   first_name VARCHAR(20) default NULL,
   last_name  VARCHAR(20) default NULL,
   salary     INT  default NULL,
   PRIMARY KEY (id)
);

A continuación se muestra el archivo de mapeo para mapear los objetos de Empleado con la tabla EMPLEADO:

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 

<hibernate-mapping>
   <class name = "Employee" table = "EMPLOYEE">
      
      <meta attribute = "class-description">
         This class contains the employee detail. 
      </meta>
      
      <id name = "id" type = "int" column = "id">
         <generator class="native"/>
      </id>
      
      <property name = "firstName" column = "first_name" type = "string"/>
      <property name = "lastName" column = "last_name" type = "string"/>
      <property name = "salary" column = "salary" type = "int"/>
      
   </class>
</hibernate-mapping>

Finalmente, crearemos nuestra clase de aplicación con el método main () para ejecutar la aplicación donde usaremos flush() y clear() métodos disponibles con el objeto Session para que Hibernate siga escribiendo estos registros en la base de datos en lugar de almacenarlos en la memoria.

import java.util.*; 
 
import org.hibernate.HibernateException; 
import org.hibernate.Session; 
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class ManageEmployee {
   private static SessionFactory factory; 
   public static void main(String[] args) {
      
      try {
         factory = new Configuration().configure().buildSessionFactory();
      } catch (Throwable ex) { 
         System.err.println("Failed to create sessionFactory object." + ex);
         throw new ExceptionInInitializerError(ex); 
      }
      ManageEmployee ME = new ManageEmployee();

      /* Add employee records in batches */
      ME.addEmployees( );
   }
   
   /* Method to create employee records in batches */
   public void addEmployees( ){
      Session session = factory.openSession();
      Transaction tx = null;
      Integer employeeID = null;
      
      try {
         tx = session.beginTransaction();
         for ( int i=0; i<100000; i++ ) {
            String fname = "First Name " + i;
            String lname = "Last Name " + i;
            Integer salary = i;
            Employee employee = new Employee(fname, lname, salary);
            session.save(employee);
         	if( i % 50 == 0 ) {
               session.flush();
               session.clear();
            }
         }
         tx.commit();
      } catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      } finally {
         session.close(); 
      }
      return ;
   }
}

Compilación y ejecución

Estos son los pasos para compilar y ejecutar la aplicación mencionada anteriormente. Asegúrese de haber configurado PATH y CLASSPATH correctamente antes de continuar con la compilación y ejecución.

  • Cree el archivo de configuración hibernate.cfg.xml como se explicó anteriormente.

  • Cree el archivo de mapeo Employee.hbm.xml como se muestra arriba.

  • Cree el archivo fuente Employee.java como se muestra arriba y compílelo.

  • Cree el archivo fuente ManageEmployee.java como se muestra arriba y compílelo.

  • Ejecute el binario ManageEmployee para ejecutar el programa, que creará 100000 registros en la tabla EMPLOYEE.