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