Tiny Star

IT

[Spring Boot] 대용량 데이터 이관 JPA vs JDBC vs MyBatis 비교

하얀지 2025. 6. 27. 12:43

대용량 이관 시 JPA vs JDBC vs MyBatis: 어떤 Writer를 선택할까?

대용량 데이터를 처리하다 보면, 성능이 코드 구조보다 우선이 될 때가 많다. 이 글은 실제 테스트를 통해 JPA, JDBC, MyBatis 각각의 Writer 성능을 비교하고, 어떤 상황에 어떤 Writer를 선택하는 게 좋은지 정리한 기록이다.

 

비교 조건

  • 약 10만 건의 BookRating 데이터를 단순 insert
  • 동적 매핑이나 조건 분기 없음
  • 각 방식으로 동일한 데이터를 동일 조건으로 insert 테스트
  • 성능 기준: 실행 시간(ms)

 

기본 구조

공통적으로 사용하는 Entity는 다음과 같다.

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BookRating {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String userId;
    private String bookId;
    private int rating;
    
    @CreatedDate
    private Date createdAt;
}

 

 

각 방식별 구현 요약

1. JDBC

  • JdbcTemplate 사용
  • batchUpdate + BatchPreparedStatementSetter 조합
  • DB와의 통신은 단 1번
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
    public void setValues(PreparedStatement ps, int i) {
        BookRating r = list.get(i);
        ps.setString(1, r.getUserId());
        ps.setString(2, r.getBookId());
        ps.setInt(3, r.getRating());
        ps.setTimestamp(4, new Timestamp(new Date().getTime()));
    }
    public int getBatchSize() {
        return list.size();
    }
});

 

 

 

2. JPA

  • EntityManager.persist() + flush() + clear()
  • 1,000건마다 flush & clear
  • 총 100번의 DB 통신 발생
for (int i = 0; i < list.size(); i++) {
    em.persist(list.get(i));
    if (i % batchSize == 0 && i > 0) {
        em.flush();
        em.clear();
    }
}
em.flush();
em.clear();

 

 

 

3. MyBatis

  • foreach 태그로 XML에서 values 묶음 처리
  • insert into ... values ... 형태로 쿼리 1회
<insert id="insertBookRatings">
    INSERT INTO book_rating (user_id, book_id, rating, created_at)
    VALUES
    <foreach collection="bookRatings" item="r" separator=",">
        (#{r.userId}, #{r.bookId}, #{r.rating}, now())
    </foreach>
</insert>

 

 

성능 테스트 결과

방식 1차 실행 2차 실행 평균 비교
JDBC 501ms 394ms 약 450ms 가장 빠름
MyBatis 1729ms 1614ms 약 1670ms 중간
JPA 60,664ms 57,702ms 약 59초 가장 느림

1차 실행

 

2차 실행

 

 

왜 이런 차이가 날까?

1. JDBC

  • PreparedStatement로 바인딩하고 바로 DB 요청
  • SQL 파싱, 객체 매핑, 트랜잭션 처리 등 부가작업 없음
  • 순수하게 DB I/O만 하기 때문에 가장 빠름

2. JPA

  • persist() 할 때는 DB에 바로 반영되지 않고, 영속성 컨텍스트에 쌓임
  • flush() 시점에 쿼리가 나감 (1000건마다 총 100번)
  • 매핑, 컨텍스트 관리, 트랜잭션 전파 등 오버헤드가 큼
  • insert 대상이 많아질수록 느려짐

3. MyBatis

  • SQL은 한번 실행되지만,
  • 내부적으로 객체 → SQL 파라미터로 변환, 리플렉션, SQL 파싱 등의 부가작업 있음
  • JDBC보다는 느리지만, 단일 쿼리로 동작하므로 꽤 빠름

 

결론

  • 단순한 대용량 Insert에는 무조건 JDBC
  • 동적 SQL, 조건 분기가 필요하면 MyBatis
  • Entity 관리, 복잡 로직 필요 시에만 JPA

단순히 "내가 평소에 익숙하니까 JPA로 한다"는 접근은 대량 데이터 이관에서는 위험하다.
성능을 기준으로 먼저 판단하고, 유지보수성과 기능 확장은 그다음 문제로 보는 것이 맞다.

 

 

개인적인 인사이트

Entity와 객체 중심으로 코딩하는 걸 목적으로 개인 프로젝트를 진행했는데, 대량 insert 작업에서는 JPA의 장점은 오히려 발목을 잡았다.
정리하면서 느낀 건 “목적에 따라 기술을 선택하는 유연함”이 정말 중요하다는 것.
빠르게 데이터를 밀어 넣는 작업이라면 JPA는 불필요하게 느려질 수 있다. 반면, 유지보수나 관계 매핑이 필요한 비즈니스 도메인에서는 JPA가 가장 깔끔하고 좋다.

이 글이 Writer 선택에 고민이 많은 분들에게 기준점이 되었으면 좋겠다.

 

 

 

 

이전에 작성했던 [대용량 이관 ItemWriter] 를 재구성한 글입니다.

top