Spring JDBC Bulk Insert 알아보기

2025. 3. 17. 19:19·Back-end/Spring

들어가기

 프로젝트 진행 중 20만 건 이상의 데이터 Row를 데이터베이스에 Insert해야 했다. JPA saveAll() 방식을 사용했을 때 저장 속도가 느려서 성능 향상이 필요하다고 느꼈고 JDBC Bulk Insert로 저장 속도를 향상시킬 수 있었다.

 

JPA saveAll()

 아래의 코드는 JPA saveAll() 코드로, while 문 안의 두 번째 라인에서 엔티티를 하나씩 save하도록 구현되어 있다. 

@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {
    Assert.notNull(entities, "Entities must not be null");
    List<S> result = new ArrayList();
    Iterator var4 = entities.iterator();

    while(var4.hasNext()) {
        S entity = (Object)var4.next();
        result.add(this.save(entity));
    }

    return result;
}

 

 따라서, 아래의 쿼리처럼 한 건마다 데이터가 삽입된다.

INSERT INTO member (name, password) VALUES ('name1', 'password1');
INSERT INTO member (name, password) VALUES ('name2', 'password2');
INSERT INTO member (name, password) VALUES ('name3', 'password3');

 

Bulk Insert

 Bulk Insert를 사용하면 여러개의 삽입할 데이터를 모아서 하나의 쿼리가 나가게 된다.

INSERT INTO member (name, password) VALUES ('name1', 'password1'), ('name2', 'password2'), ('name3', 'password3');

 

 스프링에서는 JdbcTemplate에서 batchUpdate() 메소드로 Bulk Insert를 지원한다. 아래와 같이 BulkRepository를 따로 정의해서 구현했다. (아래의 코드에서 Member 엔티티의 PK에 Auto Increment 전략을 사용하지 않고, UUID를 만들어서 지정해주었기에 id 또한 삽입의 대상이다.)

@Repository
@RequiredArgsConstructor
public class MemberBulkRepository {
    private final JdbcTemplate jdbcTemplate;

    @Transactional
    public void batchInsertMembers(List<Member> members) {
        String sql =
                "INSERT INTO member (id, name, phone_number, email, student_number, department_id, role, created_at, updated_at) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";

        try {
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    Member member = members.get(i);
                    int index = 1;
                    ps.setString(index, member.getId().toString());
                    ps.setString(++index, member.getName());
                    ps.setString(++index, member.getPhoneNumber());
                    ps.setString(++index, member.getEmail());
                    ps.setString(++index, member.getStudentNumber());
                    ps.setLong(++index, member.getDepartment().getId());
                    ps.setString(++index, "STUDENT");
                }

                @Override
                public int getBatchSize() {
                    return members.size();
                }
            });
        } catch (Exception e) {
            ...
        }
    }
}

 

 

JPA SaveAll() vs JDBC Bulk Insert

 두 개의 삽입 모두 데이터베이스는 MariaDB를 사용했고, Spring Batch를 활용하여 데이터 삽입 과정을 구성했다. Write 과정에서 데이터 저장하는 방식에만 차이를 두었다.

 

 JPA의 SaveAll()을 사용할 경우 아래와 같이 약 1분 39초가 걸렸다.

Job: [SimpleJob: [name=memberReaderJob]] launched with the following parameters: [{'time':'{value=1742205794030, type=class java.lang.Long, identifying=true}'}]
Executing step: [memberReaderStep]
Step: [memberReaderStep] executed in 1m38s798ms
Job: [SimpleJob: [name=memberReaderJob]] completed with the following parameters: [{'time':'{value=1742205794030, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 1m39s308ms

 

 JDBC Bulk Insert를 사용할 경우 아래와 같이 약 7초가 걸렸다.

Job: [SimpleJob: [name=memberReaderJob]] launched with the following parameters: [{'time':'{value=1742205981743, type=class java.lang.Long, identifying=true}'}]
Executing step: [memberReaderStep]
Step: [memberReaderStep] executed in 6s579ms
Job: [SimpleJob: [name=memberReaderJob]] completed with the following parameters: [{'time':'{value=1742205981743, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 6s920ms

 

결론

 대량의 데이터를 데이터베이스에 삽입할 일이 있다면 JDBC Bulk Insert를 사용하는 것이 약 14배 정도의 성능 향상을 보이기에 JPA를 사용하기 보다 Bulk Insert를 사용하는 것이 훨씬 성능적으로 좋다.

저작자표시 비영리 변경금지 (새창열림)
'Back-end/Spring' 카테고리의 다른 글
  • 스프링 부트 모니터링(Prometheus + Loki + Grafana, Docker)
  • @RequestBody, @ResponseBody에 대해서
  • HTTP Status Code, ResponseEntity에 대해서
  • @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor에 대해서
SiwonHae
SiwonHae
프로그래밍을 공부하고 있는 학생입니다.
  • SiwonHae
    시원해의 블로그
    SiwonHae
  • 전체
    오늘
    어제
    • 전체보기 (150) N
      • PS(Problem Solving) (95)
        • C (25)
        • C++ (33)
        • JAVA (37)
      • Algorithm & Data Structure (13)
      • Computer Science (12)
        • Network (2)
        • Design Pattern (10)
      • Back-end (6)
        • Spring (5)
      • Front-end (1)
        • React (1)
      • JAVA (4) N
      • 정보처리기사 (17)
      • SQLD (2)
  • 블로그 메뉴

    • 홈
    • 방명록
    • 글쓰기
  • 인기 글

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.0
SiwonHae
Spring JDBC Bulk Insert 알아보기
상단으로

티스토리툴바