Hibernate: procesamiento por lotes

Hibernate está almacenando los objetos recién insertados en el caché de segundo nivel. Debido a esto, siempre existe la posibilidad de OutOfMemoryException al insertar más de un millón de objetos. Pero habrá situaciones para insertar grandes datos en la base de datos. Esto se puede lograr mediante el procesamiento por lotes en hibernación.

Nota : se debe establecer hibernate.jdbc.batch_size y es un número entero entre 10 y 50. Si se establece un valor cero o negativo, se desactiva el procesamiento por lotes.

Usemos la anotación JPA @TableGenerator para generar la clave única para la entidad. Como no estamos seguros de cuántos registros se insertaron debido al lote, Hibernate deshabilita el generador de IDENTIDAD.

Proyecto de ejemplo 

Estructura del proyecto:

 

Este es un proyecto impulsado por maven

pom.xml

XML

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                       http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.gfg.hibernate.batch</groupId>
   <artifactId>hibernate-batching-example</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <properties>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
   </properties>
<dependencies>
   <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.4.15.Final</version>
   </dependency>
   <!-- mysql connector dependency -->
   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.34</version>
   </dependency>
   <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.3.0</version>
   </dependency>
 
   <!--Log4j2 API -->
   <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.11.0</version>
   </dependency>
   <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.11.0</version>
   </dependency>
</dependencies>
</project>

Para propósitos de registro, estamos usando log4j2.xml

XML

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
   <Appenders>
      <!-- Console Appender -->
      <Console name="Console" target="SYSTEM_OUT">
         <PatternLayout pattern="%d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n" />
      </Console>
   </Appenders>
   <Loggers>
      <!-- Log everything in hibernate -->
      <Logger name="org.hibernate.engine.jdbc.batch.internal.BatchingBatch" level="debug" additivity="false">
         <AppenderRef ref="Console" />
      </Logger>
      <Root level="error">
         <AppenderRef ref="Console" />
      </Root>
   </Loggers>
</Configuration>

Veamos los archivos importantes de la aplicación. Comencemos con la clase de entidad.

Producto.java

Java

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.TableGenerator;
 
@Entity
public class Product {
 
    @Id
    @TableGenerator(name = "PRODUCT_SEQ")
    // As we are doing batch insert, using TableGenerator,
    // unique key is determined
    @GeneratedValue(strategy = GenerationType.TABLE,
                    generator = "PRODUCT_SEQ")
    // data members of product
    private Long id;
 
    private String productName;
    private String productBrand;
    private int price;
    // Getter and setters
 
    public Long getId() { return id; }
 
    public void setId(Long id) { this.id = id; }
 
    public String getProductName() { return productName; }
 
    public void setProductName(String productName)
    {
        this.productName = productName;
    }
 
    public String getProductBrand() { return productBrand; }
 
    public void setProductBrand(String productBrand)
    {
        this.productBrand = productBrand;
    }
 
    public int getPrice() { return price; }
 
    public void setPrice(int price) { this.price = price; }
    // This is essentially required to avoid exceptions
    public Product() {}
}

Veamos la clase Util principal

HibernateUtil.java

Java

import com.gfg.hibernate.batch.entity.Product;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
 
public class HibernateUtil {
    private static StandardServiceRegistry registry;
    private static SessionFactory sessionFactory;
 
    public static SessionFactory getSessionFactory()
    {
        if (sessionFactory == null) {
            try {
                StandardServiceRegistryBuilder
                    registryBuilder
                    = new StandardServiceRegistryBuilder();
 
                // Configuration properties
                Map<String, Object> settings
                    = new HashMap<>();
 
                settings.put(Environment.DRIVER,
                             "com.mysql.jdbc.Driver");
 
                settings.put(
                    Environment.URL,
                    "jdbc:mysql://localhost:3306/geeksforgeeks?serverTimezone=UTC");
                // Specify mySQL credentials here
                settings.put(Environment.USER, "root");
                settings.put(Environment.PASS, "admin");
 
                settings.put(Environment.HBM2DDL_AUTO,
                             "update");
                // Set JDBC batch size. It can be set
                // between 10 and 50
                settings.put(
                    Environment.STATEMENT_BATCH_SIZE, 50);
 
                registryBuilder.applySettings(settings);
                registry = registryBuilder.build();
 
                MetadataSources sources
                    = new MetadataSources(registry);
               
                // This entity class Product is going to be
                // used for batch insert or update
                sources.addAnnotatedClass(Product.class);
                Metadata metadata
                    = sources.getMetadataBuilder().build();
 
                sessionFactory
                    = metadata.getSessionFactoryBuilder()
                          .build();
            }
            catch (Exception e) {
                if (registry != null) {
                    StandardServiceRegistryBuilder.destroy(
                        registry);
                }
                e.printStackTrace();
            }
        }
        return sessionFactory;
    }
 
    public static void shutdown()
    {
        if (registry != null) {
            StandardServiceRegistryBuilder.destroy(
                registry);
        }
    }
}

Veamos cómo se puede realizar la inserción por lotes a través de 

InsertProductBatchExample.java

Java

import com.gfg.hibernate.batch.entity.Product;
import org.hibernate.Session;
import org.hibernate.Transaction;
 
public class InsertProductBatchExample {
    public static void main(String[] args)
    {
        Session session = null;
        Transaction transaction = null;
        // Setting zero or negative number will disable the
        // batching.
        int batchSize
            = 10; // As of now, it is hardcoded to 10
        try {
            session = HibernateUtil.getSessionFactory()
                          .openSession();
            transaction = session.beginTransaction();
            // Here as a sample 100 items are inserted, but
            // it can be changed as per user choice
            for (long idx = 1; idx <= 100; idx++) {
                Product product = new Product();
                // We can use this as sample. Please change
                // according to the requirement
                product.setProductName("Product" + idx);
                product.setProductBrand("A");
                product.setPrice((int)idx * 10);
                session.save(product);
                if (idx > 0
                    && idx % batchSize
                           == 0) { // Keep on doing this
                                   // step in order to
                                   // continue and avoid
                                   // exceptions
                    session.flush();
                    session.clear();
                }
            }
            transaction.commit();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (session != null) {
                session.close();
            }
        }
 
        HibernateUtil.shutdown();
    }
}

Al ejecutar el programa anterior, en la consola, podemos ver como 

 

Comprobemos lo mismo con la salida de MySQL también

 

Ahora veamos lo mismo para las actualizaciones también.

UpdateProductBatchExample.java

Java

import com.gfg.hibernate.batch.entity.Product;
import org.hibernate.CacheMode;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.Transaction;
 
public class UpdateProductBatchExample {
    public static void main(String[] args)
    {
        Session session = null;
        Transaction transaction = null;
        // As we are going to do bulk operations, we need
        // ScrollableResults
        ScrollableResults scrollableResults = null;
        // Setting zero or negative number will disable the
        // batching.
        int batchSize = 10; // It can be between 10 and 50.
        try {
            session = HibernateUtil.getSessionFactory()
                          .openSession();
            transaction = session.beginTransaction();
            scrollableResults
                = session
                      .createQuery(
                          "from Product") // Query the table
                      .setCacheMode(CacheMode.IGNORE)
                      .scroll(
                          ScrollMode
                              .FORWARD_ONLY); // We have to
                                              // get all
                                              // records
            int count = 0;
            int price = 1;
            while (scrollableResults.next()) {
                Product product
                    = (Product)scrollableResults.get(0);
                product.setPrice(
                    price
                    + (price
                       * 10)); // update the price, this is
                               // just a sample
                price += 1;
                if (++count % batchSize
                    == 0) { // This is much required
                    session.flush();
                    session.clear();
                }
            }
            transaction.commit();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (scrollableResults != null) {
                scrollableResults.close();
            }
            if (session != null) {
                session.close();
            }
        }
        HibernateUtil.shutdown();
    }
}

 

La salida de MySQL es la siguiente:

 

Conclusión

Al usar » hibernate.jdbc.batch_size » podemos habilitar el procesamiento por lotes en hibernate session.flush() y session.clear() deben realizarse periódicamente para evitar excepciones y es una buena práctica esencialmente necesaria para el procesamiento por lotes.

Publicación traducida automáticamente

Artículo escrito por priyarajtt y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *