1. JPA의 핵심 개념
1.1. JPA의 두 가지 핵심 기능
JPA에는 객체와 테이블을 매핑하는 기술로, @Entity, @Id, @Column 등의 애노테이션으로 매핑을 정의하는 1)객체와 관계형 데이터베이스 매핑(ORM)과 엔터티를 관리하는 논리적인 저장 공간으로, JPA의 성능 최적화 기능인 2)영속성 컨텍스트(Persistence Context)로 핵심 기능이 존재한다. 이번 포스팅에서는 영속성 컨텍스트에 알아본다.
1.2. 엔터티 매니저 팩토리와 엔터티 매니저

1.3. 엔터티 매니저와 영속성 컨텍스트
영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않는다. 개발자는 엔터티 매니저(EntityManager)를 통해 이 컨텍스트에 접근한다.
// 엔터티 매니저를 통해 영속성 컨텍스트에 접근
EntityManager em = emf.createEntityManager();
em.persist(member); // 영속성 컨텍스트에 저장
1.3.1. J2SE 환경: 엔터티 매니저 ↔ 영속성 컨텍스트 (1:1 관계)

1.3.2. 스프링 환경: 여러 엔터티 매니저 ↔ 하나의 영속성 컨텍스트 (N:1 관계)

2. 영속성 컨텍스트 개념
2.1. 개념과 정의
영속성 컨텍스트는 " 엔터티를 영구 저장하는 환경"이라는 의미를 가진다. JPA가 엔터티 객체를 관리하는 가상의 데이터베이스로 이해할 수 있다.
2.2. 엔터티 매니저와의 관계
모든 엔터티 매니저는 내부에 영속성 컨텍스트를 가지고 있다. 트랜잭션 시작 시 영속성 컨텍스트가 생성되고, 트랜잭션 종료 시 함께 사라진다.
3. 엔터티 생명주기
3.1. 4가지 상태
엔터티는 총 4가지 상태를 가지며 각 상태마다 다른 특징을 가진다:
| 상태 | 설명 | 특징 |
| 비영속 | 영속성 컨텍스트와 관계 없는 상태 | 새로 생성된 객체 |
| 영속 | 영속성 컨텍스트에 관리되는 상태 | em.persist() 호출 후 |
| 준영속 | 영속성 컨텍스트에서 분리된 상태 | em.detach() 호출 후 |
| 삭제 | 삭제된 상태 | em.remove() 호출 후 |
3.2. 상태 전환 예시
// 1. 비영속 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 2. 영속 상태
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member); // 영속 상태로 전환
// 3. 준영속 상태
em.detach(member); // 영속성 컨텍스트에서 분리
// 4. 삭제 상태
em.remove(member); // 실제 삭제는 커밋 시점에 실행
4. 영속성 컨텍스트의 장점
4.1. 1차 캐시
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있어, 같은 엔터티를 여러 번 조회할 때 데이터베이스에 계속 접근하지 않는다.
// 1차 캐시에서 조회
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
em.persist(member); // 1차 캐시에 저장
Member findMember = em.find(Member.class, "member1"); // DB 접근 없이 캐시에서 조회
// 데이터베이스에서 조회
Member findMember2 = em.find(Member.class, "member2"); // DB에서 조회 후 캐시 저장


4.2. 동일성 보장
같은 트랜잭션 안에서는 같은 엔터티를 조회할 때 항상 같은 인스턴스를 반환한다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // true - 동일한 객체
이 기능은 REPEATABLE READ 격리 수준을 애플리케이션 차원에서 지원하는 효과를 가진다.
4.3. 쓰기 지연
INSERT, UPDATE, DELETE SQL을 모아서 트랜잭션 커밋 시점에 한번에 실행한다.

EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(memberA); // SQL 생성 후 쓰기 지연 SQL 저장소에 저장
em.persist(memberB); // 또 다른 SQL 저장
em.persist(memberC); // 계속 SQL 저장
// 아직 데이터베이스에 반영되지 않음
transaction.commit(); // 모든 SQL이 한번에 실행됨
쓰기 지연 SQL 저장소 동작
em.persist(memberA) → INSERT SQL (쓰기 지연)
em.persist(memberB) → INSERT SQL (쓰기 지연)
transaction.commit() → 모든 SQL 데이터베이스 실행
4.4. 변경 감지(Dirty Checking)
엔터티의 변경사항을 자동으로 감지하여 UPDATE SQL을 생성한다. em.update() 같은 메서드를 호출할 필요가 없다.

// 영속 엔터티 조회
Member member = em.find(Member.class, "member1");
// 엔티티 데이터 수정
member.setUsername("새로운이름");
member.setAge(20);
// 별도의 저장 메서드 호출 없이
// 트랜잭션 커밋 시 자동으로 UPDATE SQL 실행
변경 감지 동작 원리
- 트랜잭션 커밋 시점에 영속성 컨텍스트 플러시 발생
- 엔터티와 스냅샷(최초 조회 상태) 비교
- 변경된 필드 발견 시 UPDATE SQL 생성
- 쓰기 지연 SQL 저장소에 저장 후 데이터베이스 실행
4.5. 지연 로딩
연관된 엔터티를 실제 사용하는 시점에 조회하는 기능이다. 이는 N+1 문제를 해결하고 성능을 최적화하는 중요한 기능이다.
5. 플러시 메커니즘
5.1. 플러시 개념
플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업이다. 중요한 점은 플러시가 영속성 컨텍스트를 비우는 것이 아니라는 것이다.
5.2. 플러시 발생 시점
플러시는 다음과 같은 상황에서 발생한다:
- em.flush() 직접 호출
em.persist(member); em.flush(); // 즉시 데이터베이스에 반영 - 트랜잭션 커밋 시 자동 호출
transaction.commit(); // 커밋 전에 자동으로 flush() 실행 - JPQL 쿼리 실행 시 자동 호출
em.persist(memberA); em.persist(memberB); // JPQL 실행 시 자동 flush List<Member> result = em.createQuery( "SELECT m FROM Member m", Member.class) .getResultList();
5.3. 플러시 모드
플러시 모드를 설정하여 플러시 발생 시점을 제어할 수 있다:
// AUTO: 커밋이나 쿼리 실행 시 플러시 (기본값)
em.setFlushMode(FlushModeType.AUTO);
// COMMIT: 커밋할 때만 플러시
em.setFlushMode(FlushModeType.COMMIT);
COMMIT 모드 사용 시 주의사항
em.setFlushMode(FlushModeType.COMMIT);
em.persist(new Member("member1"));
em.persist(new Member("member2"));
// JPQL 실행 - 플러시되지 않음
List<Member> members = em.createQuery(
"SELECT m FROM Member m", Member.class)
.getResultList(); // 결과에 member1, member2 포함 안됨
6. 준영속 상태
6.1. 준영속 상태의 특징
준영속 상태는 영속성 컨텍스트가 더 이상 관리하지 않는 엔터티의 상태이다.
준영속 엔터티의 특징
- 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩 기능 사용 불가
- 식별자 값을 가지고 있음 (이미 한번 영속 상태였으므로)
- detached entity passed to persist 예외 발생 가능
6.2. 준영속 상태로 만드는 방법
준영속 상태로 전환하는 방법은 세 가지가 있다:
// 1. em.detach() - 특정 엔터티만 준영속 상태로
Member member = em.find(Member.class, "member1");
em.detach(member);
// 2. em.clear() - 영속성 컨텍스트 전체 초기화
em.clear(); // 모든 엔터티가 준영속 상태가 됨
// 3. em.close() - 영속성 컨텍스트 종료
em.close(); // 영속성 컨텍스트가 사라짐
준영속 엔터티의 재사용
Member member = em.find(Member.class, "member1");
em.detach(member); // 준영속 상태
// 준영속 엔티티를 다시 영속 상태로
em.merge(member); // 병합(merge)을 통해 다시 영속 상태로
7. 실무 적용 팁
7.1. 트랜잭션 범위와 영속성 컨텍스트
올바른 사용법
@Service
@Transactional
public class MemberService {
public void businessLogic(Long memberId) {
// 트랜잭션 시작
Member member = memberRepository.findById(memberId);
member.setName("변경된이름");
// 트랜잭션 종료 시 자동 커밋과 플러시
}
}
7.2. 변경 감지 최적화
JPA는 변경 감지 시 모든 필드를 업데이트한다. 선택적 업데이트를 원하면 @DynamicUpdate를 사용할 수 있다:
@Entity
@DynamicUpdate // 변경된 필드만 UPDATE
public class Member {
@Id
private Long id;
private String name;
private String email;
private int age;
}
7.3. 벌크 연산 시 주의사항
벌크 연산은 영속성 컨텍스트를 무시하고 직접 데이터베이스에 쿼리를 실행한다:
// 벌크 업데이트
int resultCount = em.createQuery(
"UPDATE Member m SET m.age = m.age + 1")
.executeUpdate();
// 벌크 연산 후에는 반드시 영속성 컨텍스트 초기화
em.clear();
// 다시 조회해야 최신 데이터를 가져옴
Member member = em.find(Member.class, memberId);
8. 정리
영속성 컨텍스트는 JPA의 핵심 개념으로, 다음과 같은 장점을 제공한다:
- 성능 향상: 1차 캐시로 반복 조회 최적화
- 생산성 향상: 변경 감지로 수정 코드 간소화
- 데이터 일관성: 동일성 보장과 쓰기 지연
- 트랜잭션 관리: 트랜잭션 범위 내에서 일관된 데이터 관리
영속성 컨텍스트의 동작 방식을 이해하면 JPA를 더 효과적으로 활용할 수 있고, 성능 문제를 해결하는 데도 도움이 된다. 특히 1차 캐시, 변경 감지, 쓰기 지연 같은 기능들은 JPA를 다른 데이터 접근 기술과 차별화하는 중요한 요소들이다.
'Spring > JPA' 카테고리의 다른 글
| [Basic-4] 연관관계 매핑 기초 (0) | 2026.01.06 |
|---|---|
| [Basic-3] 엔터티 매핑 (Entity Mapping) (0) | 2026.01.06 |
| [Basic-1] JPA 시작 (0) | 2026.01.05 |
| [Optimization-7] JPA: 벌크 연산 모니터링 (0) | 2025.12.27 |
| [Optimization-6] JPA: 벌크 연산과 성능 최적화 (0) | 2025.12.27 |
