4.1 ID 생성 전략
•
영속화 시 필수 ID: JPA 엔티티는 영속성 컨텍스트에서 고유하게 식별되기 위해 반드시 ID를 가져야 한다. 이는 엔티티의 생명주기 관리(예: persist, merge, remove)와 데이터베이스 매핑의 핵심이다. ID가 없으면 EntityExistsException 또는 유사한 오류가 발생할 수 있다.
•
오토 인크리먼트의 문제점:
◦
데이터베이스 오토 인크리먼트(@GeneratedValue(strategy = GenerationType.IDENTITY))는 각 엔티티에 대해 개별 INSERT 문을 실행한다. 이는 트랜잭션당 여러 SQL 호출을 유발하여 대량 데이터 처리 시 성능 병목을 초래한다.
◦
특히, JDBC 드라이버와 데이터베이스 간 왕복 시간(round-trip time)이 증가하며, 배치 작업에서 비효율적이다.
•
대안 ID 생성 전략:
◦
UUID: 애플리케이션에서 UUID를 생성하여 사용(@GeneratedValue(strategy = GenerationType.UUID))하면 DB 의존성을 줄이고 병렬 처리가 가능하다. 단, UUID는 문자열 기반이므로 인덱스 크기가 커질 수 있다.
◦
시퀀스 기반 ID: @GeneratedValue(strategy = GenerationType.SEQUENCE)와 함께 allocationSize를 설정하여 ID를 미리 할당받는다. 예: allocationSize=50은 50개의 ID를 메모리에 캐싱하여 DB 호출을 줄인다.
◦
애플리케이션 수준 ID 생성: 고유 ID 생성 로직을 애플리케이션에서 직접 관리하여 DB 의존성을 완전히 제거. 예: 고유 키 생성 라이브러리 또는 타임스탬프 기반 ID 사용.
•
대규모 배치 처리:
◦
영속성 컨텍스트 오버헤드: JPA의 영속성 컨텍스트는 엔티티 상태를 추적하므로 대량 데이터 처리 시 메모리 사용량과 성능 저하를 유발한다. 예: 수십만 개의 엔티티를 한 트랜잭션에서 관리하면 OutOfMemoryError 위험이 있다.
◦
비영속 처리: 영속화가 필요 없는 경우, JPA 네이티브 쿼리(entityManager.createNativeQuery) 또는 SQL 매퍼(예: MyBatis, jOOQ)를 사용하여 직접 SQL을 실행한다. 이는 영속성 컨텍스트를 우회하여 메모리 사용량과 성능을 최적화한다.
◦
배치 최적화: JPA를 사용할 경우, hibernate.jdbc.batch_size를 설정(예: 50)하여 배치 INSERT/UPDATE를 활성화하고, hibernate.order_inserts=true 및 hibernate.order_updates=true로 SQL 실행 순서를 최적화한다.
•
실무 예시: 대량 데이터 마이그레이션 시, JPA의 persist 대신 네이티브 INSERT 쿼리를 사용하여 처리 속도를 10배 이상 향상시킬 수 있다. 예: 100만 건 데이터 삽입 시, 배치 크기를 100으로 설정하면 단일 트랜잭션 내에서 SQL 호출이 크게 감소한다.
4.2 권장사항
•
오토 인크리먼트 지양: 고속 처리 시스템에서는 오토 인크리먼트를 피하고 시퀀스 또는 UUID를 선호.
•
배치 처리 최적화: 대규모 데이터 작업 시 영속성 컨텍스트를 최소화하고, 배치 크기와 트랜잭션 단위를 적절히 설정(예: 1000건 단위로 커밋).
•
모니터링: Hypersistence Optimizer 같은 도구를 사용하여 ID 생성 전략의 성능을 분석하고 최적화.
•
테스트: 배치 작업 전, 소규모 데이터로 네이티브 쿼리와 JPA 배치 처리의 성능을 비교 테스트하여 최적 전략 선택.
5. 관계 (Relationships)
5.1 OneToMany
•
@OrderColumn 사용:
◦
@OrderColumn은 OneToMany 관계에서 컬렉션의 순서를 데이터베이스에 저장하는 데 사용된다. 예: @OrderColumn(name = "order_idx")는 컬렉션의 인덱스를 별도 열에 저장.
◦
단점: 순서 유지로 인해 추가 UPDATE 쿼리가 발생하며, 데이터베이스 정렬은 CPU와 I/O 비용을 증가시킬 수 있다.
•
정렬 대안:
◦
프레젠테이션 계층: 소규모 데이터(예: 100건 이하)의 경우, UI 또는 API 응답에서 클라이언트 측 정렬(예: Java의 Collections.sort)을 수행하여 DB 부하 감소.
◦
비즈니스 계층: 애플리케이션 또는 도메인 로직에서 정렬(예: List.sort 또는 Stream API 사용)을 처리. 데이터 크기가 크지 않다면(예: 1000건 이하) 성능 영향 미미.
◦
쿼리 기반 정렬: 필요한 경우 JPQL 또는 Criteria API에서 ORDER BY를 사용하여 정렬. 예: SELECT o FROM Order o JOIN o.items i ORDER BY i.createdAt.
•
실무 팁: @OrderColumn은 순서가 비즈니스 요구사항에 필수적인 경우에만 사용하고, 그렇지 않으면 애플리케이션 계층에서 정렬을 처리하여 DB 오버헤드를 줄인다.
5.2 OneToOne
•
N+1 문제:
◦
OneToOne 관계는 기본적으로 즉시 로딩(FetchType.EAGER)으로 설정되어 있어, 연관 엔티티 조회 시 추가 쿼리가 발생(N+1 문제). 예: 100개의 엔티티를 조회하면 101개의 쿼리가 실행될 수 있다.
◦
지연 로딩 설정: @OneToOne(fetch = FetchType.LAZY)를 명시하고, hibernate.enable_lazy_load_no_trans=false를 설정하여 예기치 않은 프록시 초기화 방지.
◦
바이트코드 향상 없이 지연 로딩: Hibernate의 바이트코드 향상(예: @LazyToOne(LazyToOneOption.NO_PROXY))을 사용하지 않고, 명시적 JPQL 쿼리(예: SELECT e FROM Entity e LEFT JOIN FETCH e.related)로 필요한 데이터만 로드.
◦
대안 모델링: OneToOne을 OneToMany로 선언하고, 컬렉션 크기를 1로 제한. 예: List<RelatedEntity>를 사용하고 getFirstRelated() 메서드로 첫 번째 요소만 반환.
•
컬렉션 접근 제한:
◦
엔티티에서 컬렉션(List, Set)에 대한 직접 접근을 제한하고, 단일 요소 접근을 위한 메서드 제공. 예:
public class ParentEntity {
@OneToMany
private List<RelatedEntity> related = new ArrayList<>();
public RelatedEntity getRelated() {
return related.isEmpty() ? null : related.get(0);
}
}
Java
복사
◦
이 접근법은 OneToOne의 단순성을 유지하면서 OneToMany의 유연성을 활용한다.
•
실무 예시: 사용자와 프로필 간 OneToOne 관계에서, 지연 로딩과 LEFT JOIN FETCH를 사용하여 N+1 문제를 해결하면 쿼리 수가 100에서 1로 감소할 수 있다.
5.3 ManyToMany
•
명시적 관계 엔티티:
◦
@ManyToMany는 조인 테이블을 자동 생성하지만, 복잡한 쿼리와 성능 저하를 유발할 수 있다. 대신, 중간 관계 엔티티를 명시적으로 정의
◦
이점:
▪
성능: 조인 테이블의 복잡성을 줄이고, 명시적 쿼리로 최적화 가능.
▪
감사(Audit): 생성/수정 시간, 상태 등 추가 메타데이터 저장 가능.
▪
확장성: 관계에 새로운 속성(예: 권한 수준, 만료일) 추가 용이.
•
실무 팁: @ManyToMany는 간단한 관계에 적합하지만, 실무에서는 감사 로그나 추가 속성이 필요한 경우가 많으므로 관계 엔티티를 기본으로 고려. 예: 사용자-역할 관계에서 assignedAt 필드로 역할 부여 시점을 추적.
5.4 권장사항
•
지연 로딩 기본 사용: OneToOne과 OneToMany는 FetchType.LAZY로 설정하여 불필요한 데이터 조회 방지.
•
명시적 관계 엔티티: ManyToMany는 관계 엔티티로 관리하여 성능과 유연성 확보.
•
성능 모니터링: Hypersistence Optimizer를 사용하여 관계 매핑의 비효율성을 진단하고, 쿼리 실행 계획을 분석(예: EXPLAIN 사용).
•
테스트: 단위 테스트와 통합 테스트로 관계 설정과 쿼리 성능을 검증. 예: Testcontainers를 사용한 H2 DB 테스트로 N+1 문제 재현 및 해결.