> For the complete documentation index, see [llms.txt](https://leehyokyun.gitbook.io/openbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://leehyokyun.gitbook.io/openbook/2-12.-jpa-1.md).

# 2-12. JPA #1 : 핵심 속성

Application 영속성 레이어 아키텍칭의 핵심, JPA에 대하여

### 1. 1:N / N:1 Entity 관계와 Fetch Type 설정 및 1+N 문제

JPA Entity 객체의 양방향 관계를 설정할 때는 Fetch Type의 기본 동작 특성을 정확히 파악하고 있어야 1+N Problem(N+1 문제)을 예방할 수 있다.

* 기본 Fetch Type 동작: 회원(1)과 주문(N)의 관계를 표현할 때, OneToMany 관계의 기본 Fetch Type은 LAZY(지연 로딩)로 설정된다. 반면 주문 객체에서 회원 객체를 참조하는 ManyToOne 관계의 기본 Fetch Type은 EAGER(즉시 로딩)이다.
* 지연 로딩 최적화와 Target 규칙: OneToMany에서 프록시 객체의 지연 로딩을 수행할 때, 그 Target 객체 역시 지연 로딩으로 동작해야 아키텍처 일관성이 유지된다. 따라서 의존 관계가 얽힌 ManyToOne 포함 객체의 Fetch Type 역시 기본값인 EAGER에서 LAZY로 명시적 전환하는 것이 필수적이며, JSON 직렬화 시 무한 루프에 빠지는 것을 막기 위해 JsonIgnore 설정을 적용하는 것을 강력히 권장한다.
* Fetch Join을 통한 1+N 해소: 프록시 기반의 지연 로딩 연산으로 인해 발생하는 대표적인 성능 병목인 1+N Problem을 근본적으로 제거하기 위해, 처음부터 데이터베이스 JOIN 연산을 활용하여 연관 데이터를 한 번에 가져오는 즉시 로딩 전략(Fetch Join)을 채택하여 운용할 수 있다.

### 2. Entity ID 생성 전략 (Sequence / Identity / Table / UUID)

JPA 환경에서 엔티티의 식별자(PK)를 생성하는 메커니즘은 데이터베이스 엔진의 특성과 매핑 방식에 따라 4가지 형태로 세분화된다. Hibernate 측에서는 영속성 컨텍스트에 엔티티를 넣기 전에 ID 생성 전략을 확인하고, 매핑된 메타데이터를 조회하여 적절한 방식으로 식별자를 미리 추출하거나 채번하여 세터(setter) 메서드로 할당한다.

* SEQUENCE 전략과 allocationSize 최적화:
  * `@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "blocked_posts_id_seq")` 형태로 선언하며, 실제 데이터베이스의 시퀀스 객체와 연결하기 위해 `@SequenceGenerator`를 정의한다. 이때 generator name은 JPA 내부 매핑 이름이 되고, sequenceName은 실제 DB에 구축된 시퀀스 이름과 완벽히 일치해야 한다.
  * Hibernate는 네트워크 I/O 성능 향상을 위해 시퀀스 기본 값으로 `allocationSize = 50`을 사용한다. 최초 persist 호출 시 nextval을 1번만 실행하여 메모리에 50개의 ID 공간을 미리 예약(채번)해 두고, 이후 50번째 persist가 일어날 때까지는 DB에 추가 요청을 보내지 않다가 51번째에 다시 nextval을 호출하는 메모리 세이브 메커니즘이다.
  * 하지만 PostgreSQL 엔진의 경우, Hibernate의 이러한 동작 방식과 충돌하여 시퀀스 값이 실제 DB 내에서도 50씩 크게 건너뛰며 증가하는 물리적인 부작용이 동반되므로, 실무 환경에서는 allocationSize를 1로 일치시켜 설정하는 것을 권장한다.
* IDENTITY 전략:
  * `@GeneratedValue(strategy = GenerationType.IDENTITY)` 방식으로 지정한다. JPA 자체에는 식별자 시퀀스라는 독자적 개념이 없으므로 항상 대상 DB의 내부 ID 생성기(auto-increment 기능)에 전적으로 의존한다.
  * 데이터가 실제로 INSERT되어 DB 내에서 auto-increment 연산이 완료된 후에야 비로소 생성된 PK 값을 애플리케이션으로 반환받아 인식할 수 있는 쓰기 지연 제약 메커니즘을 가진다.
* TABLE 전략:
  * `@GeneratedValue(strategy = GenerationType.TABLE)` 구조를 사용하여, 시스템 내에 별도의 시퀀스 보관용 채번 테이블을 생성하고 이를 활용하여 고유 번호를 직접 관리하는 고전적 방식이다.
* UUID 전략:
  * Hibernate가 기본적으로 자체 제공하는 특수 생성 전략으로, `@Id`와 `@GeneratedValue` 아래에 private UUID id를 선언하여 구동한다.
  * 별도의 데이터베이스 통신이나 채번 테이블 조회 과정 없이, Hibernate 프레임워크 측에서 영속화 전에 고유성이 철저히 보장되는 UUID 기반의 Unique 가치 값을 즉시 직접 생성하여 id 필드에 할당하는 아키텍처적 이점을 제공한다.

### 3. Entity 다대다(N:M) 관계 해소와 폭포수(Cascade) 효과

객체 모델과 관계형 데이터베이스 간의 패러다임 불일치 중 하나인 다대다 관계를 안정적으로 설계하기 위해 중간 테이블 매핑과 영속성 전이(Cascade) 규칙이 정교하게 적용되어야 한다.

* 중간 테이블 구성을 통한 관계 해소: Account, Role, Resources 관계처럼 N:M 구조를 지닌 다대다 관계 엔티티는 데이터베이스 정규화 규칙에 맞춰 1:N 및 1:M 관계의 엔티티 구조로 완전히 분해해야 한다. 이를 위해 교차 엔티티 역할을 수행할 중간 테이블을 별도로 정의하며, JPA에서는 `@JoinTable` 어노테이션을 통해 주체 테이블의 FK(joinColumns)와 대상 테이블의 FK(inverseJoinColumns)를 구조적으로 묶어 선언한다.
* ManyToMany 설정과 지연 로딩: 다대다 직접 매핑을 부득이하게 활용할 때는 `@ManyToMany(FetchType = LAZY, cascade = MERGE)` 형태로 구조화한다. 주체 엔티티인 Account를 최초 조회할 시점에 1+N 병목 유려가 있는 하위 연관 엔티티들을 즉시 조회하지 않고 가짜 객체인 프록시(Proxy)로 먼저 채워둔 뒤, 실제 컬렉션을 호출하고 사용하는 시점에 지연 로딩으로 조회가 실행되게 만든다.
* LAZY 로딩 불가 예외 처리: 트랜잭션 범위가 완전히 종료되어 영속성 컨텍스트가 정상적으로 해제(detach)된 상태가 되면, 프록시 객체에 대한 LAZY 로딩 처리가 전면 불가능해져 예외가 발생한다. 이를 예방하기 위해 서비스 계층의 트랜잭션 안에서 연관 데이터에 미리 접근하여 초기화해 두거나, 쿼리 생성 시점부터 FETCH JOIN을 사용하여 아예 처음부터 한 몸으로 모든 연관 데이터를 바인딩하여 가져오는 아키텍처적 조치가 필요하다.
* Cascade Merge의 영속성 전이 범위: 다대다 구조에서 cascade 설정을 MERGE 타입으로 지정하면, EntityManager가 객체를 병합하고 동기화할 때 주체인 Account뿐만 아니라 하위의 Account\_Role 엔티티까지 연쇄적으로 flush 연산이 전파된다. 즉, 데이터의 수정(UPDATE) 연산은 하위 계층으로 폭포수처럼 완벽히 전파되지만, 생성(INSERT)이나 완전 삭제(DELETE)를 다루는 PERSIST 영속화 영역은 전이 대상에 포함되지 않으므로 논리적 설계 시 유의해야 한다.

### 4. Fetch Join 페이징(Pagination) 처리의 함정과 Batch Size 최적화

1:N 컬렉션 관계에서 Fetch Join과 페이징 처리를 동시에 무분별하게 혼용하면 대규모 엔터프라이즈 환경에서 심각한 OOM(Out Of Memory) 시스템 다운 타임을 유발할 수 있어 대안 전략이 강제된다.

* Fetch Join 페이징의 OOM 발생 메커니즘:
  * 10만 개의 Post 엔티티가 존재하고 각 Post별로 평균 10개의 Comment(댓글)가 얽혀 있는 상황에서 `@Query("select p from Post p join fetch p.comments")` 구조에 Pageable 객체를 실어 호출하면 결정적인 아키텍처 불일치가 발생한다.
  * 개발자가 기대하는 페이징의 기준은 상위 Root Entity인 Post 개수 단위의 LIMIT 10 OFFSET 0 연산이지만, 실제 데이터베이스에서 수행되는 JOIN 결과물은 관계 특성상 Post1 데이터가 3번 이상 중복 반복되어 결합하는 완벽한 Row 단위 기반 데이터로 출력된다.
  * JPA는 영속성 컨텍스트 내부적으로 동일 PK/엔티티 기준 `Map<PK, Entity>` 구조를 취하여 중복을 제거하고(dedup), 동일 엔티티 하위 컬렉션에 자식 객체를 추가(append)하는 내부 처리를 전개한다.
  * 이 과정에서 데이터베이스 결과 크기와 실제 메모리 적재량 사이에 mismatch가 일어나며, Hibernate 엔진은 SQL 레벨에서 제한 처리를 가하지 못하므로 데이터베이스의 데이터 10만 건 및 댓글 100만 건 전체를 메모리 풀로 일단 무조건 전부 긁어 올려 순회하며 객체를 생성한다. 결국 모든 컬렉션 데이터가 힙 메모리에 강제 유지되면서 대량의 가비지 콜렉션 부하와 함께 OOM 장애로 직결된다.
* `@BatchSize`를 활용한 보완 전략:
  * 이 문제를 안전하게 우회하기 위한 핵심 최적화 기법은 한 번에 모든 데이터를 Fetch Join으로 다 긁어오는 것이 아니라, 페이징 처리가 작동할 Root Entity인 Post 데이터만 우선 독립적으로 조회(LIMIT 및 OFFSET 정상 작동)하여 메모리 안전성을 완벽히 확보하는 것이다.
  * 이후 연관된 자식 comment 데이터를 지연 로딩할 때, 건별로 N번 반복 질의(N+1 문제)를 던지는 것이 아니라 `@BatchSize(size = 10)` 설정을 기점으로 구동한다.
  * 이 설정이 가미되면 JPA는 확보된 Post ID들을 묶어 내부적으로 `WHERE post_id IN (1, 2, 3, 4, 5, 6, ...)` 형태의 IN 절 SQL 쿼리를 구성하여 필요한 만큼만 10개 단위로 한 번에 스마트하게 일괄 추출해 온다. 컬렉션의 default인 LAZY 로딩 관계를 건실하게 유지하면서도 반복 스캔을 완벽히 방지하는 최종 최적화 아키텍처이다.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://leehyokyun.gitbook.io/openbook/2-12.-jpa-1.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
