Spring JDBC를 통한 Batch Insert

Belluga·2021년 10월 21일
1

문제점

숙박 예약 서비스 개발 중 인벤토리 추가 API를 개발하고 있었습니다.

숙소 관리자는 본인이 등록한 RoomType의 Inventory를 추가할 수 있습니다.
아래 Json 데이터를 받아 startDate 에서 endDate까지 예약 가능한 Room 갯수를 availableCount만큼 추가하게됩니다.

 {
    "startDate": LocalDate,
    "endDate": LocalDate,
    "availableCount" : Integer
}
@Service
@RequiredArgsConstructor
@Transactional
public class RoomInventoryService {

    private final RoomTypeService roomTypeService;
    private final RoomInventoryRepository roomInventoryRepository;

    public void addInventory(
        long roomTypeId, RoomInventoryAddRequestDto roomInventoryAddRequestDto, AuthUser loginUser
    ) {
        RoomType roomType =
            roomTypeService.getRoomTypeById(roomTypeId);
        VerificationUtils.verifyOwnerPermission(loginUser, roomType.getOwnerId());

        LocalDate startDate = roomInventoryAddRequestDto.getStartDate();
        LocalDate endDate = roomInventoryAddRequestDto.getEndDate();

        List<LocalDate> dates = Stream.iterate(startDate, date -> date.plusDays(1))
            .limit(ChronoUnit.DAYS.between(startDate, endDate.plusDays(1)))
            .collect(Collectors.toList());

        dates.forEach(date -> {
            RoomInventory instance =
                RoomInventory.createInstance(roomType, date, roomInventoryAddRequestDto.getAvailableCount());
            roomInventoryRepository.save(instance);
        });
    }
}

실질적으로 Inventory를 추가하는 Service 레이어 부분입니다.
해당 코드에서는 startDate에서 endDate까지 반복문을 돌며 save() 메서드를 호출합니다.


startDate가 "2021-10-01", endDate가 "2021-10-20"이라면 20개의 개별적인 INSERT 쿼리를 날리게 됩니다.
이때 쿼리를 던지고 응답받은 후에야 다음 쿼리를 전달하기 때문에 지연이 발생하게 됩니다.

임시 방편으로 최대 30일 기간의 인벤토리를 추가할 수 있다는 제약조건을 추가하였지만 좀 더 근본적인 문제를 해결하려합니다.

JPA Batch Insert

List<RoomInventory> roomInventoryList = new ArrayList<>();
        dates.forEach(date -> {
            RoomInventory instance =
                RoomInventory.createInstance(roomType, date, roomInventoryAddRequestDto.getAvailableCount());
            roomInventoryList.add(instance);
        });
roomInventoryRepository.saveAll(roomInventoryList);

saveAll() 메서드를 통해 JPA Batch Insert를 하려하였으나 실패하였습니다. 이전 포스팅에서 살펴보았듯이 현재 @GeneratedValue(strategy = GenerationType.IDENTITY)를 사용하고 있는데 이 경우 JPA Batch Insert를 할 수 없다고 합니다.

DB에 Insert가 되어야 id값을 알 수 있기 때문에 쓰기 지연이 아닌 즉각적으로 쿼리를 날릴 수 밖에 없기 때문입니다.(1차 캐시에 쌓아둘 수 없습니다)

Spring JDBC

다른 방안으로 Spring JDBC를 이용하여 Batch Insert를 진행하도록 하겠습니다. 현재 spring-boot-starter-data-jpa 라이브러리를 사용하고 있는데 이는 spring-jdbc 라이브러리 의존성을 갖고 있기 때문에 추가적인 라이브러리 없이 적용할 수 있었습니다.

@Repository
@RequiredArgsConstructor
public class JdbcRoomInventoryRepository {

    private final JdbcTemplate jdbcTemplate;

    public void batchInsertRoomInventories(List<RoomInventory> roomInventories) {
        String sql = "INSERT INTO room_inventory "
            + "(roomtype_id, inventory_date, available_count, created_at, modified_at) VALUES (?, ?, ?, ?, ?)";

        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                RoomInventory roomInventory = roomInventories.get(i);
                ps.setLong(1, roomInventory.getRoomType().getId());
                ps.setObject(2, roomInventory.getInventoryDate());
                ps.setInt(3, roomInventory.getAvailableCount());
                ps.setObject(4, LocalDateTime.now());
                ps.setObject(5, LocalDateTime.now());
            }

            @Override
            public int getBatchSize() {
                return roomInventories.size();
            }
        });
    }
}

JdbcTemplate의 batchUpdate() 메서드를 통해 Batch insert를 진행하도록 하겠습니다.


APM을 확인해본 결과 다수의 INSERT 쿼리가 나가는 것이 아닌 하나의 INSERT 쿼리가 나가는 것을 확인할 수 있었습니다.

addInventory() 메서드 수행 시간을 측정해본 결과 아래와 같은 결과를 얻을 수 있었습니다.

20개 데이터 INSERT 수행 시 1056 ms → 368 ms
90개 데이터 INSERT 수행 시 2880 ms → 490 ms

Reference

https://kapentaz.github.io/jpa/JPA-Batch-Insert-with-MySQL/#

https://homoefficio.github.io/2020/01/25/Spring-Data%EC%97%90%EC%84%9C-Batch-Insert-%EC%B5%9C%EC%A0%81%ED%99%94/

https://wave1994.tistory.com/160

https://javabydeveloper.com/spring-jdbctemplate-batch-update-with-maxperformance/

0개의 댓글