====== Hibernate Batch/Bulk Insert/Update ====== * [[http://docs.jboss.org/hibernate/core/4.3/manual/en-US/html/ch15.html|Chapter 15. Batch processing]] * [[http://frightanic.com/software-development/jpa-batch-inserts/|JPA batch inserts (using Hibernate and Spring Data JPA) • my2cents]] * [[http://stackoverflow.com/questions/6687422/hibernate-batch-size-confusion|java - Hibernate batch size confusion - Hibernate batch 작동원리]] * [[https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/5/html/Performance_Tuning_Guide/sect-Performance_Tuning_Guide-Entity_Beans-Batching_Database_Operations.html|Performance_Tuning_Guide-Entity_Beans-Batching_Database_Operations]] * [[https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/5/html/Performance_Tuning_Guide/sect-Performance_Tuning_Guide-Entity_Beans-Batch_Inserts.html|4.5.3. Batch Inserts]] * [[https://vladmihalcea.com/how-to-batch-insert-and-update-statements-with-hibernate/|How to batch INSERT and UPDATE statements with Hibernate - Vlad Mihalcea]] ===== 작동 원리 ===== * Hibernate는 평소에는 ''PreparedStatement#execute(String)''으로 쿼리를 실행하는데, batch 모드가 되면 JDBC 표준 [[http://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html#addBatch%28%29|PreparedStatement#addBatch(String)]] 메소드를 호출해주는 것 뿐이다. * ''session.flush()''를 호출한다는 것은 ''Statement.executeBatch()''를 호출한다는 의미인 듯 하다. * 기본적으로 ''addBatch/executeBatch''는 insert 성능을 향상시켜준다. [[http://java.dzone.com/articles/what-you-didnt-know-about-jdbc|What You Didn't Know About JDBC Batch]] ===== @Id 에 대한 주의점 ===== * ''**GenerationType.IDENTITY**'' ID에 대해서는 Batch Insert가 **작동하지 않는다**. ID를 리턴받아 Entity 객체에 채워줘야 하기 때문으로 보인다. * [[https://vladmihalcea.com/jpa-persist-and-merge/|How do persist and merge work in JPA]] * [[https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch-session-batch-insert|Hibernate ORM 5.4.18.Final User Guide]] * 따라서 Batch Insert 를 올바로 작동시키려면 ''@GeneratedValue(strategy = GenerationType.SEQUENCE)'' 같은 PK 값을 미리 알 수 있는 방식을 사용해야한다. > For IDENTITY columns, Hibernate cannot delay the INSERT statement until flush time because the identifier value can only be generated by executing the statement. > For this reason, Hibernate disables JDBC batch inserts for entities using the IDENTITY generator strategy. ===== 기억할 점 ===== * batch 옵션은 Hibernate가 직접 Insert 문을 ''insert into xxx (...) values(...), (....), ....'' 형태로 합쳐주는 것이 **아니다**. 단지 ''addBatch''를 할 뿐이다. * MySQL * MySQL Batch Insert는 여러건의 ''insert'' 문을 ''insert into xxx (...) values(...), (...), (...)''로 바꿔줌으로써 성능을 향상 시킬 수 있다. * [[database:mysql:jdbc|MySQL JDBC]]의 경우 JDBC URL에 ''rewriteBatchedStatements=true'' 옵션을 추가해주면 된다. * MySQL의 경우 실제로 생성된 쿼리는 ''logger=com.mysql.jdbc.log.Slf4JLogger&profileSQL=true'' 옵션으로 로그를 통해 확인할 수 있다. * [[https://github.com/kwon37xi/hibernate-batch-size-test|hibernate-batch-size-test]] MySQL기반 batch 예제 ===== 기본 설정 ===== * Hibernate Property [동시 Insert 갯수] * 지정된 갯수만큼 처리하고 트랜잭션을 닫거나 ''session.flush(); session.clear();'' 호출 * ''session.clear()''를 호출하는 이유는, insert 된 데이터들이 First Level Cache 메모리에 남아 있어서 메모리 고갈 발생할 수 있기 때문이다. * ''org.hibernate.jdbc.AbstractBatcher'' 혹은 ''org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl'' 와 ''org.hibernate.engine.jdbc.batch.internal.BatchingBatch'' 에서 남기는 로그 확인. 로거는 Hibernate 버전마다 다름. ===== 관계에 대한 Batch Insert ===== * [[http://abramsm.wordpress.com/2008/04/23/hibernate-batch-processing-why-you-may-not-be-using-it-even-if-you-think-you-are/|Hibernate Batch Processing – Why you may not be using it. (Even if you think you are)]] Hibernate Batch 사용 주의사항 * Batch 설정을 해도 아래 코드는 배치로 insert 되지 않는다. public void doBatch() { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); Cart cart = new Cart(...); customer.setCart(cart) // note we are adding the cart to the customer, so this object // needs to be persisted as well session.save(customer); if ( (i + 1) % 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(); } * 이유는 ''Customer-Cart''가 연관 관계가 있어서 하나의 ''Customer'' insert 후에 곧바로 ''Cart''가 insert되고, 그 다음에 다시 ''Customer''가 insert 되기 때문에 Customer는 Customer끼리, Cart는 Cart끼리 묶이지 않기 때문이다. * 다음 처럼 설정하면 insert 문들을 모두 정렬해서 묶어서 처리한다. true true ===== Versioned Data ===== * ''hibernate.jdbc.batch_versioned_data=true'' 로 설정하면 ''@Version'' 컬럼에 대해 대응하여 배치가 실행된다. * 즉, ''where'' 조건에 ''@Version'' 필드에 대한 조건이 들어간다. * 기본으로는 자동으로 될 수도 있는데, 일반적으로 ''true''로 하는게 안전할 수 있다. * 단, ''hibernate.jdbc.batch_versioned_data=true'' 일 경우 batch update 가 되긴하지만, 일부 JDBC 드라이버의 경우 업데이트된 필드 갯수를 오탐하는 경우도 있다고 한다. 이때는 ''false''로 바꿔야 하며, ''@Version'' 이 있을 경우 batch update 는 작동하지 않게 된다. ===== 참조 ===== * [[https://www.baeldung.com/jpa-hibernate-batch-insert-update|Batch Insert/Update with Hibernate/JPA | Baeldung]] * [[https://techblog.woowahan.com/|MySQL 환경의 스프링부트에 하이버네이트 배치 설정 해보기 | 우아한형제들 기술블로그]]