Spring Data JPA의 saveAll()메서드는 인자 값으로 받은 entity들을 저장하는 메서드이다.
실제로 실행해보면 여러 엔티티들에 대한 insert 쿼리가 각각 한 번 씩 차례대로 나가는 것을 볼 수 있다.
엔티티의 수가 적으면 상관없지만, 10,000건, 100,000건 처럼 많아지면 엔티티 1 : insert 1 방식은 부담이 될 수 있다.
인터넷을 검색해보면 hibernate.jdbc.batch_size: 50 처럼 한 번에 여러 건을 insert하는 벌크 연산이 많이 소개되어 있다.
이 방법을 사용해도 좋지만, 여기서는 Spring JPA가 아닌 JDBC를 사용한 보다 빠르고 강력한 연산인 batchUpdate()를 소개하고자 한다.
Bulk Insert
bulk insert는 여러 insert쿼리를 합쳐서 보낼 수 있는 방법이다.
3건의 데이터를 insert 한다고 하자.
INSERT INTO table1 (col1, col2) VALUES (val11, val12); INSERT INTO table1 (col1, col2) VALUES (val21, val22); INSERT INTO table1 (col1, col2) VALUES (val31, val32);
이렇게 하면 개별 insert이고,
INSERT INTO table1 (col1, col2) VALUES (val11, val12), (val21, val22), (val31, val32);
이렇게 하면 batch insert다. batch insert가 개별 insert에 비해 훨씬 효율적임을 쉽게 알 수 있다.
SaveAll() - batchUpdate()
Spring Data JPA에 정의되어 있는 saveAll()메서드는 기본적으로 개별 insert를 제공한다.
그러나 대량의 엔티티를 insert해야 하는 경우, batch insert를 사용해야 한다.
새로운 리포지토리를 생성해 Jdbc를 사용해서 saveAll()메서드를 재정의해보자.
@Repository @RequiredArgsConstructor public class FoodJdbcRepository { private final JdbcTemplate jdbcTemplate; private int batchSize = 10000; public void saveAll(List<Food> items) { int batchCount = 0; List<Food> subItems = new ArrayList<>(); for (int i = 0; i < items.size(); i++) { subItems.add(items.get(i)); if ((i + 1) % batchSize == 0) { batchCount = batchInsert(batchCount, subItems); } } if (!subItems.isEmpty()) { batchCount = batchInsert(batchCount, subItems); } System.out.println("batchCount: " + batchCount); } private int batchInsert(int batchCount, List<Food> subItems) { jdbcTemplate.batchUpdate("INSERT INTO food (`food_id`, `name`) VALUES (?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, subItems.get(i).getId()); ps.setString(2, subItems.get(i).getName()); } @Override public int getBatchSize() { return subItems.size(); } }); subItems.clear(); batchCount++; return batchCount; } }
Food라는 객체를 Food 테이블에 batch insert로 저장한다고 했을 때의 예시이다.
batchSize 변수를 통해 배치 크기를 지정하고, 전체 데이터를 배치 크기로 나눠서 Batch Insert를 실행하고, 자투리 데이터를 다시 Batch Insert로 저장한다.
-> 실제로 Spring Data JPA와 비교했을 때, batchUpdate()가 압도적인 성능 우위를 보여준다.
+) 참고로 MySQL의 경우 datasource에 rewriteBatchedStatements=true 옵션을 추가해 주어야 한다.
spring: datasource: url: jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true username: admin password: 1234 driver-class-name: com.mysql.cj.jdbc.Driver
'java > spring' 카테고리의 다른 글
[Spring] 개발, 운영 환경 별 profile 설정 (0) | 2022.01.09 |
---|---|
[Spring] FCM 서버 구축하기 (특정 시간대에 알림 보내기) (4) | 2021.12.10 |
[Spring] SpringBatch를 사용해 csv 파일 읽어 DB에 저장 (4) | 2021.08.06 |
[Spring] AWS S3, csv 파일 읽어서 DB에 저장 (0) | 2021.08.05 |
[Spring] AWS S3에서 Spring Boot로 파일 다운로드 (0) | 2021.08.05 |