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 |