DB/JPA

[Hibernate/JPA] Batching

꾸준함. 2025. 6. 4. 16:00

1. JDBC 배치 업데이트와 Hibernate 배치

 

1.1 JDBC 배치 업데이트(Batch Update) 기본 개념

  • JDBC 2.0부터 여러 개의 INSERT, UPDATE, DELETE statement를 하나의 DB 요청으로 묶어 실행 가능
    • addBatch()로 여러 statement를 쌓고
    • executeBatch()로 한 번에 실행

 

  • JDBC 배치 업데이트를 실행함으로써 얻을 수 있는 이점은 다음과 같음
    • 데이터베이스 왕복 (Round Trip) 횟수 감소
    • 트랜잭션 응답 시간 대폭 단축
    • 대량 처리 (수천~수만 건)에서 필수적 성능 최적화

 

1.2 Statement vs PreparedStatement 배치

  • Statement 배치는 각 SQL이 완전히 다른 정적 SQL일 때 실행
    • 드라이버/DB마다 지원 및 동작 방식 다름 (MySQL은 rewriteBatchedStatements 옵션 활성화 필요)

 

  • PreparedStatement 배치는 동일한 SQL, 파라미터만 다른 DML을 한 번에 실행
    • SQL 인젝션 방지, 실행 계획 캐싱, 성능 모두에 유리
    • 대부분의 DB/드라이버에서 완벽 지원

 

 

1.3 Hibernate에서 JDBC 배치 활성화

  • 기본적으로 Hibernate는 배치 업데이트를 자동 활성화하지 않기 때문에 아래와 같이 별도로 설정해줘야 함
    • 설정할 경우 INSERT/UPDATE/DELETE 시 여러 엔티티에 대해 batch prepared statement 사용 가능
    • 코드 수정 없이 설정만으로 배치 최적화 가능

 

 

  • 주의할 점은 다음과 같음
    • IDENTITY 전략(@GeneratedValue(strategy = IDENTITY)) 사용 시 INSERT 배칭 불가
    • UPDATE, DELETE에는 영향 없음
    • extended-scope persistence context에서는 세션별 batch size 리셋 필요
      • extended-scope persistence contexts는 영속성 컨텍스트가 다수의 트랜잭션에 걸쳐서 존재할 수 있음

 

1.4 Batch Size 선정 팁

  • 최적 batch size는 환경에 따라 다름
    • 10~30 사이의 값이 대부분의 OLTP 환경에서 좋은 출발점
    • 너무 크면 클라이언트/서버 메모리 부담, 트랜잭션 길어져 오히려 성능 저하 가능
    • 따라서 다양한 batch size로 벤치마킹 후 결정하는 것을 권장

 

1.5 배치 처리 실패 시 대처 방법

  • executeBatch() 실행 시 BatchUpdateException 발생 가능
    • e.getUpdateCounts()로 몇 번째 입력에서 실패했는지 추적 가능
    • 실패 행만 재처리하는 로직 설계 필요

 

1.6 Hibernate의 Bulk Processing과 Criteria API

  • 대량 UPDATE/DELETE는 아래와 같이 여러 행을 단일 SQL로 처리

 

 

  • Hibernate Criteria API로 조건부 대량 처리 가능
    • 동적 WHERE절, 상태별 대량 업데이트/삭제 등
    • 단일 트랜잭션에 너무 많은 데이터를 처리하지 말고, 배치 크기(batchSize)를 정해 반복적으로 나눠서 처리하는 것을 권장
    • 또한, 영속성 컨텍스트를 주기적으로 비워 메모리/CPU/GC 부담을 최소화하는 것을 권장
    • 장기 트랜잭션은 성능 저 및 락 문제 유발 가능

 

 

2. Batching Cascade Operations

 

2.1 Cascading과 JDBC 배치 업데이트의 관계

  • JPA/Hibernate에서는 부모-자식 연관관계에 Cascade 옵션을 설정하여 persist, merge, remove 등의 엔티티 상태 전이가 자식 엔티티까지 자동으로 전파되게 할 수 있음
  • 대량 데이터 처리 (INSERT/UPDATE/DELETE) 시 JDBC 배치 기능과 결합하면 대량의 쿼리를 최소한의 DB 왕복으로 처리하여 성능을 극대화할 수 있음

 

2.2 Cascading INSERT/UPDATE/DELETE의 배치 동작

 

가. Cascading INSERT

  • 부모 엔티티에 CascadeType.ALL, orphanRemoval=true 설정
  • 배치 크기 (hibernate.jdbc.batch_size)를 지정해도 부모와 자식 엔티티의 persist 작업이 교차되면 각 엔티티마다 별도의 INSERT 쿼리가 실행되어 진정한 배치 효과가 떨어짐
    • 해결책: hibernate.order_inserts=true 설정 시 부모, 자식 엔티티의 INSERT 쿼리를 각각 묶어서 한 번에 배치로 실행

 

 

나. Cascading UPDATE

  • 엔티티 컬렉션을 조회 후 일괄 수정하면 부모/자식 엔티티의 UPDATE 쿼리가 꼬여서 다수의 쿼리가 발생할 수 있음
    • 해결책: hibernate.order_updates=true 설정 시 같은 종류의 UPDATE 쿼리를 모아서 배치로 실행

 

 

다. Cascading DELETE

  • DELETE는 INSERT/UPDATE와 달리 배치 정렬(ordering)이 기본적으로 지원되지 않고  부모-자식 삭제가 교차되어 다수의 쿼리가 발생할 수 있음
  • 위 문제에 대한 해결책은 다음과 같음
    • 자식 객체를 먼저 수동으로 제거 후 flush 한 뒤 부모 삭제
    • JPQL bulk delete로 자식 엔티티를 한 번에 삭제한 뒤 부모 삭제
    • DB 외래키 ON DELETE CASCADE 적용하여 부모만 삭제하면 DB가 자식도 자동 삭제하도록 처리

 

 

 

2.3 버전 관리 엔티티 (Optimistic Locking)의 배치 처리

  • @Version 필드가 있는 엔티티는 동시성 제어를 위해 UPDATE/DELETE 시 버전 비교가 필요
  • Hibernate 5 이상은 Oracle 12c 등 최신 드라이버에서 hibernate.jdbc.batch_versioned_data=true가 기본 활성화되어 있어 버전 필드가 있어도 배치 처리가 안전하게 동작함

 

2.4 실무 적용 시 주의사항

  • 배치 크기와 statement ordering 옵션을 적절히 조합해야 JDBC 배치 효과를 극대화할 수 있음
  • PK를 알기 위해 각 행마다 INSERT가 즉시 실행되어야 하기 때문에 IDENTITY 전략 사용 시 INSERT 배치 불가
  • 삭제 작업은 cascade + batch로만 끝내지 말고, 필요시 DB ON DELETE CASCADE, JPQL bulk delete, 수동 flush 등 보완책 적용 필요
  • SQL 예외 발생 시 BatchUpdateException의 updateCounts로 몇 번째 작업에서 실패했는지 추적한 뒤 해당 작업만 재실행하는 것을 권장

 

3. 배치 업데이트 정리

 

3.1 기본 Hibernate Update와 Dynamic Update

  • Hibernate는 엔티티의 어떤 필드가 실제로 변경되었는지와 무관하게 UPDATE 쿼리에서 모든 컬럼을 항상 포함시킴
    • 여러 엔티티의 서로 다른 컬럼을 수정해도 모든 컬럼이 항상 포함되어 JDBC 배치로 여러 UPDATE를 한 번에 처리 가능
    • 상황에 따라 유용할 수 있으나 인덱스, 트리거, 로그, 복제 등에서 모든 컬럼이 관여하므로 오버헤드 발생

 

  • @DynamicUpdate 어노테이션을 엔티티에 적용하면 실제로 변경된 컬럼만 UPDATE 쿼리에 포함시킴

 

3.2 Detached 엔티티 merge vs update

 

가. merge (JPA 표준)

  • Detached 엔티티를 영속성 컨텍스트에 병합
  • Hibernate는 n개의 엔티티를 merge 하면 n개의 SELECT 쿼리 (최신 상태 조회)가 발생
  • 이후 상태 변화는 dirty checking으로 감지, UPDATE 배치 가능
  • 대량 병합 시 SELECT 쿼리 남발하여 비효율적

 

나. update (Hibernate 고유)

  • Detached 엔티티를 바로 세션에 다시 연결
  • SELECT 없이 UPDATE만 예약하므로 배치에 최적화
  • 대량 배치에서는 merge보다 훨씬 효율적

 

3.3 JDBC 배치의 효율과 한계

  • 기본 batch UPDATE는 여러 엔티티 UPDATE가 동일한 쿼리일 때만 JDBC 배치로 묶임
    • DynamicUpdate를 사용할 경우 쿼리가 달라져 배치 불가

 

  • 배치 크기가 너무 크면 DB/메모리 부담, 너무 작으면 효과가 미미함
    • 10~30 사이가 실전에서 가장 많이 쓰임

 

4. 문자열로 SQL 쿼리를 동적으로 생성하면 안 되는 이유

  • SQL문을 문자열로 직접 조립 (즉, 사용자 입력값을 그대로 SQL에 삽입)하면, SQL 인젝션(SQL Injection) 공격에 취약해짐
    • 공격자는 입력값에 악의적인 SQL 구문 (i.g. '; DROP TABLE post_comment; --)을 넣어 데이터를 조작, 삭제하거나, DB 시스템을 마비시키는 등 심각한 보안 문제가 발생할 수 있음

 

  • 파라미터 바인딩 (PreparedStatement의 ? 플레이스홀더 등)을 사용해야 SQL 인젝션을 원천적으로 차단할 수 있음

 

 

참고

인프런 - 고성능 JPA & Hibernate (High-Performance Java Persistence)

 

반응형

'DB > JPA' 카테고리의 다른 글

[Hibernate/JPA] 트랜잭션과 동시성 제어 패턴  (0) 2025.06.09
[Hibernate/JPA] Fetching  (0) 2025.06.09
[Hibernate/JPA] Statement  (0) 2025.06.04
[Hibernate/JPA] 영속성 컨텍스트  (0) 2025.06.04
[Hibernate/JPA] 상속  (0) 2025.06.02