JPA를 공부할 때 가장 먼저 붙잡을 축은 두 가지다.
- 객체 모델과 관계형 데이터베이스를 어떻게 매핑하는가
- 영속성 컨텍스트가 엔티티의 생명주기와 변경을 어떻게 관리하는가
기존 공부 개요에서도 이 둘을 가장 중요하다고 적었다. 다만 뒤의 목차에는 JPA 표준, Hibernate 같은 구현체, Spring Data JPA 기능이 한 목록에 섞여 있었다. 이 경계를 먼저 나누면 무엇을 배우는 중인지 훨씬 분명해진다.
JPA·Hibernate·Spring Data JPA는 같은 것이 아니다
| 이름 | 역할 | 공부할 때의 질문 |
|---|---|---|
| Jakarta Persistence(JPA) | Java 객체와 관계형 데이터의 영속성 관리를 정의한 표준 | 엔티티·영속성 컨텍스트·JPQL이 어떤 계약을 가지는가? |
| Hibernate ORM | JPA 표준을 구현하는 대표적인 ORM 구현체 | 표준 기능이 실제로 어떻게 동작하고 어떤 확장을 제공하는가? |
| Spring Data JPA | JPA 위에 Repository 추상화와 쿼리 편의 기능을 제공 | 반복적인 저장소 코드를 어떻게 줄이고 조회 방식을 선택하는가? |
JpaRepository, 메서드 이름 기반 쿼리, Specification, Query by Example, Projection은 JPA 표준 자체의 목차가 아니라 주로 Spring Data JPA에서 배우는 기능이다. 반대로 영속성 컨텍스트, 엔티티 매핑, JPQL, Criteria API는 JPA 표준의 핵심 범위다.
1단계: ORM이 해결하려는 불일치부터 본다
어노테이션 사용법부터 외우면 매핑 오류가 생겼을 때 원인을 찾기 어렵다. 먼저 객체 모델과 관계형 모델의 차이를 작은 예제로 확인한다.
- 객체의 참조와 테이블의 외래 키
- 객체 상속과 테이블 구조
- 객체 동일성·동등성과 데이터베이스 기본 키
- 연관 객체 탐색과 SQL 조인
- 객체 변경과
INSERT·UPDATE실행 시점
여기서 목표는 “SQL을 쓰지 않는 것”이 아니다. 객체의 변경을 어떤 SQL과 트랜잭션으로 연결하는지 설명할 수 있어야 한다.
2단계: 엔티티와 값 매핑을 익힌다
작은 도메인 하나로 다음 순서를 밟는다.
@Entity와 기본 키를 정의한다.- 필드·프로퍼티 접근 방식과 기본 타입을 매핑한다.
@Embeddable같은 값 타입을 구분한다.- 단방향 연관관계부터 만든다.
- 양방향이 정말 필요할 때만 연관관계의 주인을 정한다.
- cascade와 orphan removal을 엔티티 생명주기 기준으로 판단한다.
기존 글에서 정리한 값 타입 메모는 이 단계에 놓을 수 있다. 값 타입은 식별자를 가진 엔티티와 달리 소유한 엔티티의 생명주기에 종속된다는 관점으로 읽는 편이 좋다.
3단계: 영속성 컨텍스트와 트랜잭션을 연결한다
영속성 컨텍스트는 단순한 캐시가 아니다. 엔티티를 관리 상태로 추적하고, 같은 식별자에 대한 객체 동일성을 유지하며, 변경 내용을 flush 시점에 SQL로 반영하는 작업 단위다.
확인할 개념은 다음과 같다.
- 비영속·관리·분리·삭제 상태
- 1차 캐시와 동일성 보장
- 변경 감지(dirty checking)
- flush와 commit의 차이
persist,find,remove,detach,merge의 역할- 트랜잭션 범위와 영속성 컨텍스트 범위
영속성 관리 정리를 읽을 때도 “메서드가 무엇을 하는가”보다 “지금 이 엔티티를 누가 관리하고, SQL은 언제 발생하는가”를 먼저 질문한다.
4단계: 연관관계·프록시·조회 비용을 함께 본다
연관관계 매핑은 어노테이션만 맞춘다고 끝나지 않는다. 객체 그래프를 탐색할 때 실제 SQL이 언제, 몇 번 실행되는지 확인해야 한다.
- 지연 로딩과 즉시 로딩
- 프록시 초기화 시점
- 영속성 컨텍스트 밖에서 지연 로딩 객체에 접근하는 문제
- N+1 조회가 생기는 조건
- fetch join과 EntityGraph의 사용 범위
- 컬렉션 fetch join과 페이징을 함께 쓸 때의 제약
기존 프록시와 연관관계 정리는 이 단계에 연결된다. 학습할 때는 SQL 로그와 반환된 객체 상태를 함께 보는 것이 좋다. 다만 SQL 로그를 켰다는 사실만으로 조회가 효율적이라고 단정하지 말고, 호출 횟수와 실행 계획까지 필요에 따라 확인한다.
5단계: JPQL과 Criteria로 객체 대상 쿼리를 이해한다
JPQL은 테이블이 아니라 엔티티와 그 속성을 대상으로 쿼리한다. 다음 범위를 먼저 익히면 Spring Data의 쿼리 기능도 이해하기 쉬워진다.
- 기본 선택·조건·정렬·집계
- 연관관계 경로 탐색과 명시적 조인
- fetch join
- DTO 생성자 표현식
- 페이징과 벌크 연산의 주의점
- 타입 안전성이 필요한 동적 쿼리에서 Criteria API의 역할
벌크 UPDATE·DELETE는 영속성 컨텍스트의 관리 상태를 우회할 수 있다. 실행 후 관리 중인 엔티티와 데이터베이스 값이 어긋날 수 있다는 점을 별도로 확인한다.
6단계: Spring Data JPA 조회 방식을 문제에 맞게 고른다
기존 개요에 있던 조회 기능은 다음처럼 나누면 된다.
| 방식 | 적합한 상황 | 한계·주의점 |
|---|---|---|
| 쿼리 메서드 | 조건이 짧고 이름으로 의도가 드러날 때 | 조건이 늘면 메서드 이름이 읽기 어려워진다. |
@Query |
JPQL이나 native query를 직접 통제할 때 | 문자열 쿼리의 리팩터링 비용을 고려한다. |
| Specification | 조건을 조합하는 동적 쿼리 | 조인·fetch·페이징 조합을 검증해야 한다. |
| Query by Example | 단순한 속성 일치 검색 | 중첩된 논리 조건과 컬렉션 조건에는 제약이 있다. |
| Projection | 필요한 필드나 읽기 모델만 반환 | 중첩 속성의 조인과 반환 타입 규칙을 확인한다. |
| Native query | DB 고유 기능이나 복잡한 SQL이 필요할 때 | 이식성·매핑·페이징·count query를 직접 챙길 수 있다. |
하나의 방식을 정답으로 두기보다 검색 조건의 복잡도, 반환 형태, 변경 가능성, SQL 통제 수준으로 선택한다.
7단계: 작은 모델을 끝까지 검증한다
강의를 따라 기능별 예제를 여러 개 만드는 것보다, 주문과 회원처럼 작은 모델 하나를 정해 다음 흐름을 반복하면 개념이 연결된다.
엔티티 매핑
→ 트랜잭션 안에서 저장·조회
→ 연관관계 탐색
→ 발생한 SQL 확인
→ N+1과 fetch 전략 비교
→ JPQL·Projection으로 조회 모델 분리
→ 예외와 롤백 후 상태 확인
각 단계에서 기록할 것은 성공한 코드만이 아니다.
- 예상한 SQL과 실제 SQL이 달랐는가?
- flush는 어느 동작에서 일어났는가?
- 같은 식별자를 조회했을 때 객체 동일성이 유지됐는가?
- 트랜잭션 밖에서 접근한 연관 객체는 어떤 상태였는가?
- 테스트가 데이터베이스 제약과 실제 쿼리 횟수까지 확인하는가?
이 체크리스트는 앞으로 공부할 순서다. 여기 적은 모든 항목을 한 프로젝트에서 이미 검증했다는 뜻은 아니다.
공부 순서를 한 줄로 줄이면
관계형 모델과 ORM의 차이
→ 엔티티·값·연관관계 매핑
→ 영속성 컨텍스트와 트랜잭션
→ 프록시·fetch 전략·N+1
→ JPQL·Criteria
→ Spring Data JPA 조회 방식
→ SQL과 트랜잭션을 포함한 테스트
JPA 공부의 목표는 어노테이션을 많이 아는 것이 아니라, 객체 상태의 변화가 언제 어떤 SQL과 트랜잭션으로 이어지는지 예측하는 것이다.
참고 자료
'배움과 성장 > 백엔드·데이터' 카테고리의 다른 글
| JPA 값 타입 정리: @Embeddable과 @ElementCollection의 경계 (2) | 2022.12.28 |
|---|---|
| JPA 프록시·지연 로딩·cascade·orphanRemoval: 헷갈리는 경계 (0) | 2022.12.28 |
| MySQL 버전 선택 기준: 5.7·8.0 유지 단계와 8.4·9.7 LTS (0) | 2022.12.03 |
| Spring Boot 버전 선택: Java·Jakarta·지원 기간으로 고르는 법 (0) | 2022.12.03 |
| 우아콘 2022 회원 시스템 이벤트 아키텍처: 3개 계층과 Outbox 학습 노트 (0) | 2022.10.21 |
댓글