✅ JPA 영속성 컨텍스트
1. 영속성 컨텍스트
1) 개념
- 트랜잭션 내에서 데이터베이스를 한 번이라도 갔다 온 엔티티를 관리
- 로직을 하나의 트랜잭션으로 묶기 위해 @Transactional을 사용
- 엔티티(Entity)를 영속적으로 저장하는 환경(엔티티를 잠깐 보관하고 관리하는 작업대)
- 엔티티의 생명주기를 관리하고, 애플리케이션과 데이터베이스 사이에서 수많은 최적화 작업을 해주는 논리적인 공간(메모리 공간)
- 최종 결과물만 DB에 반영
2) 1차 캐시와 동일성(Identity) 보장
- 1차 캐시
- 일종의 Map 형태의 캐시 저장소를 갖고 있음
- 엔티티가 영속성 컨텍스트의 1차 캐시에 저장
- 1차 캐시에서 확인 > 캐시에 동일한 ID의 엔티티 객체가 있으면, DB를 조회하지 않고 캐시의 엔티티를 즉시 반환(빠름)
- 동일성 보장
- 같은 트랜잭션 안에서 같은 ID로 조회한 엔티티는 항상 동일한 메모리 주소의 인스턴스(== 비교 시 true)임이 보장
- 불필요한 DB 조회를 줄여 성능을 크게 향상
3) 변경 감지
- 우리는 UPDATE 쿼리를 직접 작성할 필요가 없음
- 트랜잭션이 커밋되기 직전, 1차 캐시에 있는 모든 엔티티의 최초 상태(스냅샷)와 현재 상태를 비교
- 두 상태가 다르다면(필드값 변경), JPA는 변경된 내용을 감지하고 자동으로 UPDATE SQL을 생성하여 DB에 반영
= 더티 체킹
4) 쓰기 지연
- save() 한다고 바로 DB에 저장 X
- 순서: 1차 캐시 저장 > INSERT SQL 만들어서 모음(업데이트, 삭제 쿼리도 쌓임) > 트랜잭션이 커밋되는 순간, 한 번에 DB로 전송 = Flush
- 장점: 한 번에 처리하므로, 네트워크 비용을 줄이고 DB와 상호작용하는 횟수를 최소화하여 성능을 최적화
- Flush: 영속성 컨텍스트 내용을 DB에 반영 > 롤백시 취소됨
2. EntityManager
1) 개념
- 내부에 영속성 컨텍스트(Persistence Context)라는 공간 O
- 핵심 인터페이스 / 애플리케이션과 데이터베이스 사이에서 엔티티 객체를 관리하고 실제 데이터베이스 작업을 수행하는 객체
- 모든 엔티티 관리(저장: persist(), 조회: find(), 삭제: remove())
2) 구조

- Persistence Unit (설계도)
- 역할: DB 연결 정보, 엔티티 정보 등 설정 모음
- 어떤 재료(엔티티)를 사용하고, 어떤 공장(데이터베이스)에 연결할지에 대한 모든 정보 O
- EntityManagerFactory (공장)
- 역할: 설계도를 읽어서 EntityManager를 만들어줌 > 단 하나만 생성
- 생성 비용: 최초에 생성될 때, 설정 파일을 읽고, 데이터베이스 커넥션 풀을 만드는 등 리소스를 많이 사용하는 무거운 작업을 수행 > 여러 번 생성하는 것은 매우 비효율적
- 스레드 안정성: 여러 스레드가 동시에 접근해도 안전
- EntityManager (작업자)
- 역할: 실제로 DB 작업함(수정, 삭제, 조회) / 데이터베이스 커넥션을 사용하여 SQL을 실행
- 생성 비용: 저렴
- 생명주기: 데이터베이스 트랜잭션과 동일 / 하나의 비즈니스 로직(요청)이 시작될 때 생성되고, 끝날 때 닫히는 짧은 생명주기
- 스레드 안전성: 여러 스레드가 동시에 접근하면 안 됨 / 각 요청마다 개별 EntityManager를 사용
- 설계도 바탕 > 공장 짓기 > 공장에서 필요할 때마다 작업자 생산 > 작업자가 작업대에서 일함
3. 트랜잭션 전파
1) 개념
- 새로운 작업(메서드)을 시작해야 하는데, 혹시 이미 진행 중인 다른 작업(트랜잭션)이 있다면 어떻게 해야 할까를 정하는 것
- @Transactional 어노테이션을 사용해 메서드를 트랜잭션 안에서 실행할 수 O > propagation 옵션으로 여러 가지 전략을 설정
2) propagation 옵션
- REQUIRED (default): 부모 트랜잭션 있으면 → 같이 참여 / 없으면 → 새로 만듦
- REQUIRES_NEW: 부모가 있든 없든 새 트랜잭션 생성 / 반드시 기록되어야 하는 부가 기능에 사용(실패해도 로그 저장)
- NESTED: 부모 트랜잭션이 있으면, 그 안에 중첩된 트랜잭션(savepoint)을 만듬 / 부모 롤백시 같이 롤백 / 부모 안에서 부분 취소 가능 / 실패할 가능성이 있는 부분을 따로 분리하여 예외 처리를 하고 싶을 때 유용
- SUPPORTS: 부모 트랜잭션이 있으면 참여 / 없으면 트랜잭션 없이 그냥 실행 / 주로 읽기 전용
- NOT_SUPPORTED: 항상 트랜잭션 없이 실행 / 외부 API 호출, 이메일 발송
- MANDATORY: 부모 트랜잭션 반드시 있어야 함 / 없으면 예외 발생 / 어떤 트랜잭션의 일부로서만 동작해야 하는 메서드임을 강제하고 싶을 때 사용
- NEVER: 트랜잭션 있으면 예외 발생 / 실수 방지용 안전장치
4. 트랜잭션 격리 수준
1) 개념
- 동시에 실행되는 여러 트랜잭션들이 서로 어느 정도까지 격리되어 실행될지를 결정하는 데이터베이스 설정
- 격리 수준(낮은 순서대로): READ UNCOMMITED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
2) 격리 단계
- READ UNCOMMITTED: 커밋 안 된 것도 읽음 / Dirty Read 발생 / 거의 안 씀
- READ COMMITTED: 커밋된 것만 읽음 / Non-Repeatable Read 발생 = 같은 트랜잭션 안에서 두 번 조회했는데
중간에 다른 트랜잭션이 수정해서 값이 달라짐 - REPEATABLE READ: 자신의 트랜잭션이 생성되기 이전의 트랜잭션에서 COMMIT이 된 데이터만 읽 / 같은 트랜잭션 안에서 조회 > 값이 바뀌지 않음 / MySQL 기본값 / Phantom Read 가능 =조건 검색했을 때 행이 새로 생기거나 사라짐(유령 행)
- SERIALIZABLE: 항상 Lock을 걸고 데이터를 조회 / 성능 문제로 사용 X
✅ JPA 연관관계 매핑
1. 연관관계
1) 개념
- 엔티티(테이블) 간의 관계
2) Primary Key와 Foreign Key
- Primary Key: 기본 키는 테이블에 있는 각 행(row)을 유일하게 식별할 수 있는 고유한 값
- Foreign Key: 외래 키는 한 테이블의 컬럼이 다른 테이블의 기본 키를 참조하는 것
3) 단방향 관계
- 한쪽 객체에서만 다른 쪽 객체를 참조할 수 있는 관계
- Member는 자신이 속한 Team을 알지만, Team은 자신에게 속한 Member들을 모르는 관계
4) 양방향 관계
- Member는 Team을 알고, Team 역시 자신에게 속한 Member 목록을 아는 관계
5) 연관관계 주인
- FK를 가진 쪽이 주인
- Member 테이블에 team_id 있음 > Member가 연관관계의 주인
- 주인 X: mappedBy 속성을 사용 / Team 쪽에서 관계를 바꿔도 DB에는 반영X
2. JPA 연관관계 매핑
1) @OneToOne(1:1)
- 핵심: FK를 어디에 둘지 선택(더 중요한 쪽)
2) @ManyToOne(N:1)
- 핵심: FK를 가진 쪽 / 연관관계의 주인이 되는 쪽
- 여러 멤버 : 하나의 팀
- 단방향 지향
- 관계를 FK 하나로만 표현하기 때문에, 결국 모든 연관관계는 FK를 가진 @ManyToOne 구조로 표현가능
3) @OneToMany(1:N)
- 반드시 @ManyToOne이 먼저 존재
- FK를 가지지 않기 때문에 @OneToMany는 단독으로 의미 없음 / 조회용
4) @ManyToMany (N:M)
- 자동으로 중간 테이블 생성
- 코드에서 직접 제어 못함 / 컬럼 추가 못함 / 유지보수 어려움 / 숨겨진 SQL 발생
- 직접 중단 테이블 만듬
3. JPA 조회 전략(FetchType)
1) 개념
- JPA가 하나의 엔티티를 조회할 때, 그 엔티티와 연관된 다른 엔티티를 언제 데이터베이스에서 함께 조회할지를 결정하는 옵션
2) EAGER (즉시 로딩)
- 부모 엔티티를 조회하면, 연관된 자식 엔티티까지 한 번에 모두 조회
- Member 조회하면 Team도 같이 JOIN해서 가져옴
- 장점: 편함
- 단점: 필요 없어도 모두 조회 / 성능 문제 / N+1 문제
3) LAZY (지연 로딩)
- 부모 엔티티를 조회할 때는 연관된 자식 엔티티를 가져오지 않고, 실제로 그 데이터가 필요한 시점에 비로소 DB에서 조회
- Member 조회 시 Team은 안 가져옴 / 대신 프록시 객체 넣어둠
- 프록시 객체: 가짜(대리) 객체
4. @ManyToOne 옵션
@Getter
@Entity
@Table(name = "members")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// ...
}
@Getter
@Entity
@Table(name = "teams")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// ...
}
1) @ManyToOne 옵션
- fetch = FetchType.LAZY: 연관 엔티티(Team)를 프록시로 지연 로딩
- optional: 이 연관관계가 필수인지(=null 허용 X) 여부를 정의(JPA)
- true (기본) = null 허용
- false = null 불허
2) @JoinColumn 옵션
- name: FK 컬럼 이름
- unique: 유니크 제약 부여 여부(기본: false)
true로 설정하면 여러 Member가 같은 Team을 가리킬 수 없게 되어 사실상 1:1 제약 - nullable: FK 컬럼의 NULL 허용 = optional과 동일(DB)
- foreignKey: FK 제약 메타데이터
- @ForeignKey(name = "fk_member_team", value = ConstraintMode.XXX) 형식
- name: DB에 생성될 FK 제약 이름
- value: FK 생성 모드
CONSTRAINT: 실제 FK 제약 생성
NO_CONSTRAINT: FK 제약 미생성
3) FK를 바라보는 관점 전환
- FK는 데이터 무결성 제약조건
- 제약조건을 설정할 수도 있고, 설정하지 않을 수도 있는 것
'🔌 SPARTA > Courses' 카테고리의 다른 글
| Spring Security와 SecurityContext (0) | 2026.03.03 |
|---|---|
| JWT와 Filter (0) | 2026.03.03 |
| [숙련 Spring] Spring Data JPA (0) | 2026.02.11 |
| [숙련 Spring] Spring Boot 활용하기 (0) | 2026.02.10 |
| 스탠다드반 3회차: 실전 SQL (0) | 2026.02.09 |