JPA 심화 학습: 영속성 컨텍스트의 비밀과 조회 최적화 전략
“JPA를 사용한다는 것은 단순히 라이브러리를 쓰는 것이 아니라 객체와 데이터베이스 사이의 영속성 컨텍스트라는 거대한 흐름을 제어하는 일이다.”
오늘 학습한 JPA의 핵심 동작 원리와 성능 최적화 전략을 정리해 본다. 이론을 넘어 실무에서 마주할 수 있는 디테일한 설정들이 왜 중요한지 다시 한번 깨닫는 시간이었다.
1. 영속성 컨텍스트와 쓰기 지연의 ‘예외’
영속성 컨텍스트에는 쿼리를 모았다가 한 번에 보내는 ‘쓰기 지연’ 기능이 있다. 하지만 모든 상황에서 동작하는 것은 아니다.
- IDENTITY 전략의 특성:
@GeneratedValue(strategy = GenerationType.IDENTITY)를 사용하면 ID 값을 데이터베이스에서 생성해야만 객체를 식별할 수 있다. - 즉시 쓰기 발생: 따라서
save()를 호출하는 즉시 DB에INSERT쿼리가 발생하며 쓰기 지연이 일어나지 않는다. 그래야만 생성된 ID를 즉시 조회하여 영속성 컨텍스트가 엔티티를 관리할 수 있기 때문이다.
2. 연관 관계 매핑과 데이터 정합성
엔티티 간의 관계를 설정할 때는 성능과 비즈니스 로직을 모두 고려해야 한다.
- 기준 설정:
@ManyToOne은 많은 쪽(N) 엔티티를 기준으로 작성하며 기본 전략은EAGER(즉시 로딩)이다. 반면@OneToMany의 기본 전략은LAZY(지연 로딩)이다. - Optional & Nullable의 조화:
optional은 JPA 레벨에서의 null 허용 여부nullable은 DB 레벨에서의 null 허용 여부다.- 두 설정은 반드시 같은 전략을 가져야 하며 “게시글이 있어야 댓글이 존재할 수 있다”는 비즈니스 규칙처럼 존재의 필수값인 경우 엄격하게 관리해야 한다.
3. 프록시(Proxy): ‘책의 목차’를 활용한 최적화
FetchType.LAZY를 사용하면 실제로 연관 데이터를 즉시 가져오지 않고 프록시 객체를 매핑해 둔다.
- Proxy Initialization:
- 프록시는 마치 ‘책의 목차’ 와 같다.
- 특정 정보를 찾을 때 책 전체를 읽지 않고 목차만 먼저 보듯이 JPA도 조회 시 목차(Proxy)만 살짝 가져온다.
- 우리가 실제로 그 데이터를 펼쳐볼 때(사용할 때) 내부적으로 데이터를 로드하여 실제 객체처럼 행동하게 되는데 이것이 JPA가 제공하는 최적화 기술이다.
4. 프로젝션(Projection): 정밀 타격 조회
전체 엔티티가 아닌 원하는 필드만 골라서 객체로 받아오는 기술이다.
- 도입 효과:
- 성능 최적화: 필요한 데이터만 가져와 네트워크 트래픽, DB 부하, 메모리 사용량을 감소시킨다.
- 타입 안정성: 인터페이스나 DTO를 사용해 코드의 가독성과 안정성을 높인다.
- 구현 방식:
- 인터페이스 기반: getter 메서드만 선언하면 Spring Data JPA가 실행 시점에 구현 클래스를 만들어준다.
- 클래스 기반(DTO): 조회할 필드 순서와 타입이 일치하는 생성자가 반드시 필요하다.
5. @DataJpaTest: 가벼운 테스트 환경 구축
전체 스프링 세계를 띄우지 않고 오직 JPA Repository의 동작만 테스트하고 싶을 때 사용한다.
“왜 @DataJpaTest인가?” 거대한 애플리케이션 안에서
@Service,@Controller등 불필요한 의존성을 배제하고 JPA 관련 빈들만 띄워 테스트 속도를 획기적으로 높여준다.
오늘의 회고
프록시를 ‘책의 목차’에 비유하며 이해하니 JPA가 왜 지연 로딩을 사용하는지 그 철학이 더 와닿았다. 이론적으로만 알던 IDENTITY 전략의 즉시 쓰기 발생 이유나 optional과 nullable을 맞춰야 하는 실무적 디테일을 정리하며 한 단계 더 성장했음을 느낀다. 결국 좋은 코드는 프레임워크의 메커니즘을 정확히 알고 그 의도에 맞게 설계할 때 나온다는 것을 잊지 말자.
References
- Project Repository: w00lam/my-scheduler-v2
- 자바 ORM 표준 JPA 프로그래밍 (김영한 저)
- Spring Data JPA 공식 레퍼런스 가이드 ```