본문 바로가기
Spring

[JPA] JPA batch insert가 다건을 한 번에 지정해도 row마다 insert가 발생하는 건 해결방안

by 루에 2022. 9. 26.
반응형

A db에서 데이터를 가져와 B db에 넣는 배치 작업을 수행할 때 두가지 문제 발생

 

문제1. insert 하기 전에 select가 실행됨

문제2. repository.saveAll(List) 을 통해 다량의 데이터를 넣는데, 멀티 insert가 되지 않고 행별로 insert가 됨

(십만 개를 넣으면 10만번 select, insert 가 수행되는 상황)

 

select 되는 원인 분석

여러 fk의 조합으로 pk가 되어 있는데, 이 경우 이미 key값이 정해진 것으로 판단하여 insert할 때 update를 해야하는지 확인하는 로직이 존재. 그래서 아래 구문에서 isNew()가 false가 되어 merge가 수행되면서 발생한다.

@Transactional
	@Override
	public <S extends T> S save(S entity) {
		Assert.notNull(entity, "Entity must not be null.");

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

해결 >>

entity에 Persistable<>를 implemets 하고 isNew()를 오버라이드 한다. 필요에 따라 getId()도 오버라이드 한다.

참조 문서에서는 다른 케이스로 insert 되는 것까지 고려하여 isNew가 false가 되야하는 상황도 고려되어 있으니 필요시 참조. 내 경우에는 해당 table에 insert는 일일 배치만 되기 때문에 필요가 없어서 적용하지 않았다.

@Entity
@Table(name = "table name")
public class ReservedSeat implements Persistable<String> {
.
.
.
.
	@Override
	public boolean isNew() {
		return true;
	}
}

 

멀티 insert가 되지 않는 현상에 대한 해결법 리스트

1

properties에 몇가지 옵션을 추가한다.

jpa.properties.hibernate.order_inserts: true

jpa.properties.hibernate.order_updates: true

jpa.properties.hibernate.jdbc.batch_size: 10000

 

spring.datasource.hikari.data-source-properties.rewriteBatchStatements: true

를 추가하라고 한다.

 

적용 후 >> 안됨. 실패.

 

2.

postgresql 은 옵션명이 rewriteBatchedInserts 라고 한다. jdbc url에 추가

 

적용 후 >> 안됨. 실패

 

3.

GenerationType.IDENTITY 옵션을 사용하면 내부적으로 batch insert를 사용할 수 없다고 함

>> 사용하지 않는 중

 

4.

Spring에서 제공하는 jdbcTemplate으로 sql을 날린다.

 

적용 후 >> 적용됨. 성능 향상됨

 

다른 방법이 있는지는 모르겠지만, 구글에는 mysql에 대한 정보만 있고 postgresql에 대한 설정값이 명확하게 나와있는 것은 찾을 수 없다. jdbcTemplate은 정상 적용 확인되었으니 해당 방법을 사용한다.

 

적용 코드

batchUpdate()는 여러 개가 있고, 각각 파라미터가 다르지만 아래 함수가 가장 나아보인다.

아래처럼 만들고 entityJdbcRepository.saveAll(List)를 호출하면 된다.

그리고 JPA와 병행해서 배치를 수행할 경우, @Transactional 을 꼭 붙여서 실패 케이스에 대응할 수 있도록 하자.

@Repository
@AllArgsConstructor
public class EntityJdbcRepository {
    private final JdbcTemplate jdbcTemplate;
 
    @Transactional
    public void saveAll(List<Entity> datas) {
        jdbcTemplate.batchUpdate(
        "INSERT INTO table (`id`, `name`) VALUES (?, ?)",
        datas,	// insert할 데이터 리스트
        10000,	// 1회에 진행할 배치 사이즈(10만개 데이터라면 만 개 씩 10번 돌아감)
        (ps, i) -> {
            ps.setString(1, subItems.get(i).getId());
            ps.setString(2, subItems.get(i).getName());
        }
    }
}

 

최종 적용 건.

@Repository
@AllArgsConstructor
public class PurCndJdbcRepository {
	private final JdbcTemplate jdbcTemplate;

	/**
	 * 배치 insert
	 *
	 * @param purCnds PurCnd 콜렉션 객체
	 * @return batchSize로 나눠서 insert 수행한 횟수
	 */
	@Transactional
	public int batchSaveAll(List<PurCnd> purCnds) {
		return batchSaveAll(purCnds, Constants.DEFAULT_BATCH_SIZE);
	}

	@Transactional
	public int batchSaveAll(List<PurCnd> purCnds, int batchSize) {
		return jdbcTemplate.batchUpdate(
			"insert into pur_cnd(pur_cnd_cd, pur_cnd_nm, sup_cd, sales_chnl_cd, "
				+ "frst_reg_dtm, frst_reg_usr_id, frst_reg_pgm_nm, last_mod_dtm, "
				+ "last_mod_usr_id, last_mod_pgm_nm, vld_pur_cnd_yn) "
				+
				"values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
			purCnds,
			batchSize,
			(ps, argument) -> {
				ps.setString(1, argument.getPurCndCd());
				ps.setString(2, argument.getPurCndNm());
				ps.setString(3, argument.getSupCd());
				ps.setString(4, argument.getSalesChnlCd());
				ps.setString(5, DateUtil.now());
				ps.setString(6, ThreadContext.getCurrentUsrId());
				ps.setString(7, ThreadContext.getCurrentScrId());
				ps.setString(8, DateUtil.now());
				ps.setString(9, ThreadContext.getCurrentUsrId());
				ps.setString(10, ThreadContext.getCurrentScrId());
				ps.setString(11, argument.getVldPurCndYn());
			}).length;
	}
}

 

 

 

참조

https://taesan94.tistory.com/266

 

Spring Data Jpa Insert 할 때 Select가 나가네..

문제 상황 설계한 Entity의 id가 Auto Increament값이 아니다. 생성자가 호출되는 시점에 fk의 조합으로 생성된다. makeReservedSeatId 함수에서 만들어진다. @Entity @Table(name = "reserved_seat") public clas..

taesan94.tistory.com

https://velog.io/@qotndus43/Batch-Insert

 

Spring JDBC를 통한 Batch Insert

숙박 예약 서비스 개발 중 인벤토리 추가 API를 개발하고 있었습니다.숙소 관리자는 본인이 등록한 RoomType의 Inventory를 추가할 수 있습니다.아래 Json 데이터를 받아 startDate 에서 endDate까지 예약 가

velog.io

 

반응형

댓글