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 |