JPA에서 JdbcTemplate로 bulk update 최적화


원래 Spring Data JPA의 messageRepository.saveAll(messages)를 사용해 Message 객체 리스트를 데이터베이스에 저장했으나, saveAll이 개별 save를 반복하는 방식이라 대량 작업에 비효율적이라는 것을 알게 됐다.

당연히 saveAll은 벌크 업데이트일 줄 알았던 나는 큰 충격을 받았다. 리소스 관리를 위해 Redis를 쓰고 최적화에 노력했는데, 정작 saveAll로 많은 SQL 쿼리를 발생시키고 있었다. 그래서 JdbcTemplate를 이용해 벌크 업데이트를 할 수 있다는 것을 찾아내고 해결했다.


문제

JPA의 비효율성:
  • 처음에 쓴 messageRepository.saveAll(messages) (JPA)는 각 Message마다 별도의 INSERT 문을 보내 느렸고, 데이터베이스 왕복이 너무 많았다.
JPA 인터페이스 한계:
  • MessageRepository가 JPA 인터페이스라 JdbcTemplate를 직접 사용할 수 없었다. 인터페이스는 필드나 로직을 가질 수 없기 때문이다.

해결한 방법

새 클래스 생성:
  • JdbcTemplate로 벌크 삽입을 처리하기 위해 새 클래스 MessageBulkRepository를 만들었다:
  • @Repository public class MessageBulkRepository를 정의하고, JdbcTemplate 필드를 추가 (@RequiredArgsConstructor로 주입).
  • 이 클래스는 JPA 리포지토리와 별도로 대량 삽입을 담당.
bulk update 메서드:
  • bulkInsertMessages(List messages)jdbcTemplate.batchUpdate로 구현.
  • SQL: "INSERT INTO message (chat_room_id, sender_id, content, enrolled_at) VALUES (?, ?, ?, ?)"로 작성, message_id는 자동 증가라 제외.
  • BatchPreparedStatementSetter 사용:
  • setValues에서 각 Message 객체의 필드를 SQL 플레이스홀더에 매핑.
  • getBatchSize() { return messages.size(); }로 배치 크기 설정.
코드:
@Repository
@RequiredArgsConstructor
public class MessageBulkRepository {

    private final JdbcTemplate jdbcTemplate;

    public void bulkInsertMessages(List messages) {
        String sql = "INSERT INTO message (chat_room_id, sender_id, content, enrolled_at) " +
                "VALUES (?, ?, ?, ?)";

        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Message message = messages.get(i);

                ps.setLong(1, message.getChatRoom().getChatRoomId());
                if (message.getUser() != null) {
                    ps.setLong(2, message.getUser().getUserId());
                } else {
                    ps.setNull(2, java.sql.Types.BIGINT);
                }
                ps.setString(3, message.getContent());
                ps.setTimestamp(4, message.getEnrolledAt());
            }

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

}


Posted on 2025.03.11