Transaction 정책 및 Hibernate/jpa bulk save

2019.03.26 18:38Spring/Spring Data JPA

데이터를 한번에 여러 데이터를 저장할때 ,  bulk save로 검색하면 여러 샘플이 나오는데

10만건(특정)의 데이터를 50(특정)건수 만큼씩 끊어서 Transaction을 적용하고 저장하고 싶을때 참고하면 좋을거 같다.



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


1. class 나 inferface 에선언하여 사용한다.

2. 자기 참조는 불가능 하다. (클레스를 분리하여 외부 호출로 사용 ) - AOP proxy 방식때문이다.

3. private method 에서는 동작되지 않음

...



자기 참조로 호출이 불가능 하기때문에 약간의 꼼수라고 해야 할까요 ? 

내부 무명 클레스를 이요하거나,  DL 방식으로 Class를 불러와서 사용하면 가능 하다. 

예제는 DL 방식으로 불러와서 사용하는 예제이다.



이렇게 해도 Transaction 전파 전략에 따라 원하는 기능이 구현될수도 있고 안될수도 있다. 

이에 Transaction 전파 전략을 확인하고 갈것. (NESTED 와 REQUIRES_NEW )


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





## @Transation Option
# name - 메서드명
메서드의 이름이며 와일드 문자(*)를 사용할 수 있습니다. 예로 'get*'은 get으로 시작하는 모든 메서드를 가리키며
'*get'은 get으로 끝나는 모든 메서드를 가리킵니다.

# timeout - 제한시간 (기본값 : -1)
트랜잭션의 제한시간을 설정합니다. DB가 해당기능을 지원해야 하며 기본값으로는 -1인 제한시간 없음이 설정됩니다.

# propagation - 전파옵션 (기본값 : REQUIRED)
REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성합니다.
REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성되도록 합니다.
SUPPORT : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 nontransactionally로 실행됩니다.
MANDATORY : 부모 트랜잭션 내에서 실행되며 부모 트랜잭션이 없을 경우 예외가 발생됩니다.
NOT_SUPPORT : nontransactionally로 실행하며 부모 트랜잭션 내에서 실행될 경우 일시 정지 됩니다.
NEVER : nontransactionally로 실행되며 부모 트랜잭션이 존재한다면 예외가 발생합니다.
NESTED : 해당 메서드가 부모 트랜잭션에서 진행될 경우 별개로 커밋되거나 롤백될 수 있습니다.
둘러싼 트랜잭션이 없을 경우 REQUIRED와 동일하게 작동합니다.

# isolation - 격리수준 (기본값 : DEFAULT)

DEFAULT : DB에서 설정된 기본 격리 수준을 따릅니다.
SERIALIZABLE : 가장 높은 격리수준을 가지며 사용시 성능 저하가 있을 수 있습니다.
READ_UNCOMMITTED : 커밋되지 않은 데이터에 대한 읽기를 허용합니다.
READ_COMMITTED : 커밋된 트랜잭션에 대해 읽기를 허용합니다.
REPEATABLE_READ : 동일한 필드에 대한 다중 접근 시 동일한 결과를 얻을 수 잇는 것을 보장합니다.

# read-only - 읽기전용 (기본값 : false)

해당 메서드는 오로지 읽기에만 사용됩니다. INSERT나 UPDATE, DELETE문은 허용되지 않습니다.
만약 쓰기나 삭제가 실행될 경우 에러를 발생시킵니다.

#rollback-for - 예외처리 (기본값 : RuntimeException)

특정 예외가 발생했을 경우에 롤백되도록 설정합니다.
설정하지 않을 경우 오로지 RuntimeException을 상속받은 예외에만 롤백처리를 해줍니다.

#no-rollback-for - 예외처리 (기본값 : 없음)


http://egloos.zum.com/springmvc/v/499291
http://www.nextree.co.kr/p3180/




구현 예제

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();

}
}
}