개발/spring-batch

MyBatisBatchItemWriter 와 일반 itemWriter

방푸린 2024. 12. 24. 17:38
반응형

환경: springbatch5, java17, mysql

 

MyBatisBatchItemWriter<GmahjongRanking> writer = new MyBatisBatchItemWriterBuilder<GmahjongRanking>().sqlSessionFactory(casualDb)
    .statementId(Constant.GAME_MAPPER + "insertGmahjongDayRank")
    .build();
<insert id="insertGmahjongTotalRank" parameterType="com.hangame.batch.casual.application.model.gmahjong.ranking.GmahjongRanking">

INSERT INTO GAME (regdate, memberid, wincnt, defeatcnt, slevel, rating, ranking, avatarid, nickname, oranking)
VALUES (#{registerDate}, #{memberId}, #{winCount}, #{defeatCount}, #{level}, #{rating}, #{ranking}, #{avatarId}, #{nickname}, #{oRanking})

</insert>

insert 문이 이렇게 있을 때 insert문이 1개만 나가는지, 청크 수만큼 나가는지 궁금해졌다.

insert 문이 1개만 나간다는 의미는 values 뒤로 n개 붙은 문이 한번 나가는 것이고

청크 수 만큼 나간다는 것은 insert 문 자체가 n 개 있다는 뜻.

 

MyBatisBatchItemWriter의 write 함수를 살펴보면 아래와 같다.

while 문으로 청크를 돌아서 sql을 만들고 들고 있다가 한 번에 실행한다.

ExecutorType.BATCH로 설정된 SqlSessionTemplate에서는, update() 메서드 호출 시 쿼리를 바로 실행하지 않고 내부 배치 큐에 저장하고 flushStatements()를 호출하면, 지금까지 배치 큐에 저장된 모든 SQL 문을 한 번에 실행

  • 장점:
    1. 네트워크 요청 최소화: 각 SQL 문을 개별적으로 실행하지 않고, 배치로 묶어서 처리
    2. 성능 향상: 배치 처리 시 JDBC 드라이버가 여러 쿼리를 내부적으로 최적화
  • 주의점:
    1. 메모리 사용량: 배치 큐에 저장된 쿼리가 많아질 경우 메모리 사용량이 증가
    2. 트랜잭션 관리: 배치 처리 중 하나의 쿼리가 실패하면, 전체 배치가 롤백

 

 

그럼 values 뒤로 쫙 붙여서 한번에 쏘고 싶다면?

우선 mapper를 수정하고

@Bean(INSERT_NINE_RATING_RANKING_WRITER)
@StepScope
public ItemWriter<BadukEnrichedRanking> insertNineRatingRankingWriter() {
    return chunk -> {
      @SuppressWarnings("unchecked") var items = (List<BadukEnrichedRanking>) chunk.getItems();
      var splittedNineRankings = ListUtil.splitList(items, SPLIT_LIST_SIZE);

      splittedNineRankings.forEach(badukNineRankingMapper::insertNineRankings);
    };
}

MyBatisBatchItemWriter를 안 쓰고 수동으로 itemWriter를 만든 후 

chunk를 sublist로 쪼갠 후 foreach 에 연결시킨다.

그러면 1 insert 의 values에 여러 개가 붙고 각 호출이 개별적인 SQL 실행을 하게 된다.

혹시 배치 방식으로 바꾸려면..

return chunk -> {
    @SuppressWarnings("unchecked")
    var items = (List<BadukEnrichedRanking>) chunk.getItems();
    var splittedNineRankings = ListUtil.splitList(items, SPLIT_LIST_SIZE);

    // Batch 처리 활성화
    try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        var mapper = sqlSession.getMapper(BadukNineRankingMapper.class);

        splittedNineRankings.forEach(mapper::insertNineRankings);

        // 배치 실행
        sqlSession.flushStatements();
        sqlSession.commit();
    }
};

 


MyBatisBatchItemWriter(ExecutorType.BATCH):

  • ExecutorType.BATCH 모드에서는 하나의 SqlSession을 열고 여러 쿼리를 실행한 후 한 번에 flushStatements()를 호출하여 쿼리들을 모아서 데이터베이스에 전송
  • 이 모드는 SQL 세션을 한 번만 열고, 여러 개의 쿼리를 하나의 트랜잭션 내에서 실행. 세션을 닫기 전에 모든 쿼리가 메모리에 쌓이고, flushStatements()를 호출하여 한 번에 실행되므로 성능 면에서 효율적

forEach 방식 (기본 SqlSession):

  • 반면에 forEach를 사용하여 각각의 항목을 처리하는 경우, 매번 update 또는 insert가 실행될 때마다 SqlSession을 생성
  • 이 방식은 각각의 쿼리가 별도의 세션을 사용하거나, 적어도 별도의 쿼리 실행이 이루어지는 방식. 즉, SQL 세션을 매번 열고 update 또는 insert를 실행한 후 세션을 닫고, 다시 열어서 쿼리를 실행하는 방식
728x90
반응형