본문 바로가기
Spring/Spring Data JPA

Hibernate/ spring jpa bulk save

by 아이티.파머 2019. 3. 26.
반응형

데이터를 한번에 여러 데이터를 저장할때 ,  bulk save로 검색하면 여러 샘플이 나오는데
10만건(특정)의 데이터를 50(특정)건수 만큼씩 끊어서 Transaction을 적용하고 저장하고 싶을때 참고하면 좋을거 같다.

Spring 에서 Transaction을 사용하기 위해서는 몇가지 주의할점이 있다. 

1. class 나 inferface 에선언하여 사용한다.
2. 자기 참조는 불가능 하다. (클레스를 분리하여 외부 호출로 사용 ) - AOP proxy 방식때문이다.
3. private method 에서는 동작되지 않음

자기 참조로 호출이 불가능 하기때문에 약간의 꼼수라고 해야 할까요 ? 
내부 무명 클레스를 이요하거나,  DL 방식으로 Class를 불러와서 사용하면 가능 하다. 
예제는 DL 방식으로 불러와서 사용하는 예제이다.

이렇게 해도 Transaction 전파 전략에 따라 원하는 기능이 구현될수도 있고 안될수도 있다. 
이에 Transaction 전파 전략을 확인하고 갈것. (NESTED 와 REQUIRES_NEW )

전략이 이리도 중요할줄이야...

Transantion 옵션 참고  2022.04.20 - [Spring/Spring Data JPA] - Spring Transactional 옵션

JPA Bulk SAVE 구현 예제

package com.aereport.core.crawler.media.v1.common.jpa;

import com.aereport.config.ApplicationContextProvider;
import com.aereport.jpa.entity.AbstractCommonInsightEntity;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
* <pre>
* Description :
*
*
* </pre>
*
* @author skan
* @version Copyright (C) 2019 by skan. All right reserved.
* @since 2019-03-26
*/
@Repository
@Scope(scopeName = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Slf4j
public class AdsBulkRepository {

@Value("${spring.jpa.properties.hibernate.jdbc.batch_size:500}")
private int batchSize;

@PersistenceContext
EntityManager entityManager;

//@Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRES_NEW)
@Transactional(transactionManager = "transactionManager", propagation = Propagation.NESTED)
public <T extends AbstractCommonInsightEntity> void bulkDataSave(List<T> entities) throws DataAccessException {

  AtomicInteger adGroupInsightIndex = new AtomicInteger(1);
  AdsBulkRepository adsBulkRepository = (AdsBulkRepository) ApplicationContextProvider.getBean("bulkRepository");
  List<T> datas = new ArrayList<>();
  entities.forEach(entity -> {
  	Optional.ofNullable(entity).ifPresent(
        item -> {
        datas.add(item);
        if (adGroupInsightIndex.get() > 0 && adGroupInsightIndex.get() % batchSize == 0) {
          try {
              adsBulkRepository.adGroupInsightSaveBulk(datas);
          } catch (Exception e) {

            log.error("ad data = {}", datas);
            log.error("bulk data save error ", e);

          } finally {
              datas.clear();
          }
        }
        adGroupInsightIndex.getAndIncrement();

      });
  });

	adsBulkRepository.adGroupInsightSaveBulk(datas);

}


//@Transactional(transactionManager = "transactionManager", propagation = Propagation.NESTED)
@Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRES_NEW)
public <T> void adGroupInsightSaveBulk(List<T> entitys) {
  try {
    entitys.forEach(adGroupInsightDailyEntity ->
    entityManager.merge(adGroupInsightDailyEntity)
    );
  } finally {
    entityManager.flush();
    entityManager.clear();
    entityManager.close();

    }
  }
}
반응형