프로젝트에서는 할인 정책과 관련된 업데이트를 매일매일 해야 했다.
이때 벌크 Update를 쓸 수 있을 것이다.
많은 양의 데이터를 DB에 Insert,Update하는 상황이면
DB와 애플리케이션 간에 발생하는 I/O를 줄이는 게 좋다.
insert 쿼리 10만 개보다는
BULK 형태 N번의 insert 쿼리가 성능상 많은 이점을 가질 수 있다.
JPA에는 saveAll()이라는 메서드가 있다.
다만, Spring Batch를 이용하는 게 아니라 단건 별로 SQL을 작성해서 DB로 보낸다.
엔티티 객체가 DB에 insert되면 ID가 맵핑돼야 한다. BULK 형태의 INSERT 쿼리를 쓰면 모든 Entity의 ID를 알 수 없다고 한다. INSERT된 Entity의 ID를 알기 위해서는 row별로 INSERT쿼리가 실행될 수밖에 없다. 즉, 건마다 sql 쿼리가 나가야 한다.
하이버네이트는 ID 자동 생성 전략을 쓰면 batching insert를 지원하지 않는다.
1)부모 Entity를 insert하고 생성된 ID 반환
2)자식 Entity에서는 부모 ID를 fk로 채워서 insert하지만, batch insert를 하면 어느 자식이 부모에게 매핑돼야 하는지 알 수 없다
spring.jpa.properties.hibernate.jdbc.batch_size=개수
ID자동 생성이 아니면 배치 사이즈를 조절하여 벌크 Insert를 사용할 수 있다.
ID 생성 전략을 sequence로 해도 되지만, MySQL은 ID 생성전략에서 sequence를 지원하지 않아서 방법이 복잡했다.
그래서 기존 테이블을 그대로 둔 채 JDBC를 사용하기로 했다.
MySQL에서 Bulk Insert를 사용하려면, DB-URL에 'rewriteBatchedStatements=true' 파라미터를 추가해야 한다.
@Repository
@RequiredArgsConstructor
@Slf4j
public class AccommodationBatchRepository {
private final JdbcTemplate jdbcTemplate;
public void startDiscount(List<Long> ids) {
String sql = "UPDATE Accommodation SET is_on_sale = true WHERE id = ?";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, ids.get(i));
}
@Override
public int getBatchSize() {
return ids.size();
}
});
}
public void endDiscount(List<Long> ids) {
String sql = "UPDATE Accommodation SET is_on_sale = false WHERE id = ?";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, ids.get(i));
}
@Override
public int getBatchSize() {
return ids.size();
}
});
}
}