HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
✍🏻
Learnary (learn - diary)
/
🥈
Bulk Operation
🥈

Bulk Operation

progress
Done
Tags
Database
🏗️ Build Up💨What❓Why✅How📌 REFER

🏗️ Build Up

    💨What

      ❓Why

        ✅How

          • file structure
          notion image
          • Repository Code
          package com.kdt.instakyuram.article.postimage.domain; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class PostImageCustomRepositoryImpl implements PostImageCustomRepository { private final int DEFAULT_BATCH_SIZE = 5; private final JdbcTemplate jdbcTemplate; public PostImageCustomRepositoryImpl(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void batchSaveAll(List<PostImage> postImages) { final String sql = "INSERT INTO post_image " + "(post_id, original_file_name, server_file_name, size, path) " + "VALUES (?, ?, ?, ?, ?)"; List<PostImage> batchs = new ArrayList<>(); for (PostImage postImage : postImages) { batchs.add(postImage); if (batchs.size() % DEFAULT_BATCH_SIZE == 0) { this.insertBulk(batchs, sql); } } if (!batchs.isEmpty()) { insertBulk(postImages, sql); } } private void insertBulk(List<PostImage> postImages, String sql) { jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setLong(1, postImages.get(i).getPost().getId()); ps.setString(2, postImages.get(i).getOriginalFileName()); ps.setString(3, postImages.get(i).getServerFileName()); ps.setLong(4, postImages.get(i).getSize()); ps.setString(5, postImages.get(i).getPath()); } @Override public int getBatchSize() { return postImages.size(); } } ); postImages.clear(); } }
          • DB source 및 Test 설정
          packagecom.kdt.instakyuram.article.postimage.domain; importjava.util.List; importjava.util.stream.Collectors; importjava.util.stream.IntStream; importjavax.sql.DataSource; importorg.junit.jupiter.api.DisplayName; importorg.junit.jupiter.api.Test; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.boot.jdbc.DataSourceBuilder; importorg.springframework.boot.test.context.TestConfiguration; importorg.springframework.context.annotation.Bean; importorg.springframework.jdbc.core.JdbcTemplate; importorg.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; importorg.springframework.test.context.junit.jupiter.SpringJUnitConfig; importcom.kdt.instakyuram.article.post.domain.Post; importcom.zaxxer.hikari.HikariDataSource; @SpringJUnitConfig(JdbcSource.class) classPostImageCustomRepositoryImplTest { @Autowired PostImageCustomRepositorypostImageCustomRepository; @Test @DisplayName("batch test") voidmakePostImages() { List<PostImage> postImages =IntStream.rangeClosed(1, 10) .mapToObj(count -> { String fileName = "test" + count; String path = "/post"; returnPostImage.builder() .post(Post.builder().id(1L).build()) .originalFileName(fileName) .serverFileName(fileName) .size(12345L) .path(path) .build(); }).collect(Collectors.toList()); postImageCustomRepository.batchSaveAll(postImages); } } @TestConfiguration classJdbcSource { @Bean publicDataSourcedataSource() { returnDataSourceBuilder .create() .url("jdbc:mysql://localhost:3306/kyuram?serverTimezone=Asia/Seoul&rewriteBatchedStatements=true&profileSQL=true&c=Slf4JLogger") .username("root") .password("password") .type(HikariDataSource.class) .build(); } @Bean publicNamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSourcedataSource) { return newNamedParameterJdbcTemplate(dataSource); } @Bean publicJdbcTemplate jdbcTemplate(DataSourcedataSource) { return newJdbcTemplate(dataSource); } @Bean publicPostImageCustomRepositorypostImageCustomRepository() { return newPostImageCustomRepositoryImpl(jdbcTemplate(dataSource())); } }
          • 쿼리 콘솔 결과
            • 5개씩 2번의 배치 - 총 10건
          notion image

          📌 REFER

          JPA Batch Insert와 JDBC Batch Insert
          모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와 이 모든 내용을 담고 있는 블로그 가 있습니다. ) MySQL을 사용하면서 Batch Insert를 수행하기 위해서는 2가지 방법이 있습니다. 많은 블로그에서 다루고 있는 JPA + Batch Insert를 MySQL에서 사용하기 위해서는 ID 전략을 Table 전략으로 수정해야 합니다.
          JPA Batch Insert와 JDBC Batch Insert
          https://velog.io/@backtony/JPA-Batch-Insert%EC%99%80-JDBC-Batch-Insert
          (해결) Spring Boot, JPA - Batch Insert
          Batch Insert란? DB에 레코드를 N(N&gt;&#x3D;2) 삽입 시, insert문을 N번 반복하는 것이 아닌 하...
          (해결) Spring Boot, JPA - Batch Insert
          https://blog.naver.com/PostView.naver?blogId=hsy2569&logNo=222151651874
          (해결) Spring Boot, JPA - Batch Insert
           
          • 외국 자료
          Why does Hibernate disable INSERT batching when using an IDENTITY identifier generator
          Hibernate tries to defer the Persistence Context flushing up until the last possible moment. This strategy has been traditionally known as transactional write-behind. The write-behind is more related to Hibernate flushing rather than any logical or physical transaction. During a transaction, the flush may occur multiple times.
          Why does Hibernate disable INSERT batching when using an IDENTITY identifier generator
          https://stackoverflow.com/questions/27697810/why-does-hibernate-disable-insert-batching-when-using-an-identity-identifier-gen
          Why does Hibernate disable INSERT batching when using an IDENTITY identifier generator
          • Batch Insert 성능 향상기 1,2
          Batch Insert 성능 향상기 1편 - With JPA - Yun Blog | 기술 블로그
          Batch Insert 성능 향상기 1편 - With JPA - Yun Blog | 기술 블로그
          Batch Insert 성능 향상기 1편 - With JPA - Yun Blog | 기술 블로그
          https://cheese10yun.github.io/jpa-batch-insert/
          Batch Insert 성능 향상기 1편 - With JPA - Yun Blog | 기술 블로그
          Batch Insert 성능 향상기 2편 - 성능 측정 - Yun Blog | 기술 블로그
          JPA + MySQL + GenerationType.IDENTITY 조합으로는 Batch Insert를 사용할 수 없습니다. 자세한 내용은 Batch Insert 성능 향상기 1편 - With JPA 에서 자세하게 정리했습니다. JPA의 단일 insert와, batch insert의 성능적인 차이가 얼마나 발생하는지 측정해보고 batch insert를 지원하는 새로운 솔루션을 찾아 성능 측정을 진행한 포스팅입니다.
          Batch Insert 성능 향상기 2편 - 성능 측정 - Yun Blog | 기술 블로그
          https://cheese10yun.github.io/spring-batch-batch-insert/
          키전략
          JPA - 기본 키 매핑 전략(@Id)
          1)직접 할당 : 기본 키를 애플리케이션에서 직접 엔티티클래스의 @Id 필드에 set해준다. - IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.(ex MySQL - AUTO INCREMENT...) - SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.(ex Oracle sequence...) - TABLE : 키 생성 테이블을 사용한다.(ex 시퀀스용 테이블을 생성해서 테이블의 기본키를 저장하고 관리한다.)
          JPA - 기본 키 매핑 전략(@Id)
          https://coding-start.tistory.com/m/71
          JPA - 기본 키 매핑 전략(@Id)
          • 시퀀스 테이블 성능이슈까지 다뤄줌 ㅎ
          JPA Batch Insert with MySQL
          JPA에서 Batch Insert가 되지 않아서 그 이유를 확인한 과정을 공유합니다. Spring, Kotlin, MySQL 환경기준으로 작성했습니다. JPA Batch Insert를 확인하기 전에 먼저 JDBC에서는 어떻게 처리해야 하는지 먼저 살펴보겠습니다. 대량의 데이터를 DB에 insert 할 때면 JDBC 배치를 사용해서 SQL 쿼리를 일괄 처리하는 것이 효율적일 수 있습니다. 간단하게 10,000건을 등록하는 예제 코드를 만들어 봤습니다.
          JPA Batch Insert with MySQL
          https://kapentaz.github.io/jpa/JPA-Batch-Insert-with-MySQL/#
          JPA Batch Insert with MySQL
          • mybatis (쓰기지연 x)과 비교
          JPA의 쓰기지연 기능 확인해보기 (transactional write-behind)
          JPA의 특징중 하나는 영속성 컨텍스트 (Persistence Context)내에서 쓰기지연(transactional write-behind)이 발생한다는 것입니다. 이 글에서는 JPA와 Mybatis를 이용해 쓰기지연을 했을때와 안했을 때를 비교해보려고 합니다. (Mybatis는 쓰기지연을 하지 않습니다.) 쓰기지연이란? (transactional write-behind) 영속성 컨텍스트에 변경이 발생했을 때, 바로 데이터베이스로 쿼리를 보내지 않고 SQL 쿼리를 버퍼에 모아놨다가, 영속성 컨텍스트가 flush 하는 시점에 모아둔 SQL 쿼리를 데이터베이스로 보내는 기능입니다.
          JPA의 쓰기지연 기능 확인해보기 (transactional write-behind)
          https://soongjamm.tistory.com/150
          JPA의 쓰기지연 기능 확인해보기 (transactional write-behind)
          • DataSource 설정
          JdbcTemplate의 Batch Insert 구현시, rewriteBatchedStatements 옵션을 true로 설정하여 성능 문제 해결
          Spring Batch Application과 JPA, JDBC를 함께 사용하여 Batch 작업을 처리해야 하는 상황이 있었습니다. 이때 MySQL Connector/J (JDBC Reference) - Configuration Properties for Connector/J 에서 제공하는 기능들을 몰랐기에, 원하는 대로 동작하지 않았던 문제가 있었습니다. 데이터를 가공해서 1,000 ~ 150,000개의 데이터를 Pay 테이블에 넣어야 하는 상황이었습니다.
          JdbcTemplate의 Batch Insert 구현시, rewriteBatchedStatements 옵션을 true로 설정하여 성능 문제 해결
          https://hyos-dev-log.tistory.com/1
          JdbcTemplate의 Batch Insert 구현시, rewriteBatchedStatements 옵션을 true로 설정하여 성능 문제 해결