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