JPA 기본편 강의 (영속성 관리) 정리 노트
JPA의 핵심은 크게 두 가지다.
즉, 영속성 컨텍스트를 명확하게 이해하면 JPA 동작 원리를 이해할 수 있다.
JPA는 엔티티 매니저 팩토리(EntityManagerFactory) 로부터
엔티티 매니저(EntityManager) 를 만들어 사용한다.
→ 영속성 컨텍스트는 엔티티 매니저와 1:1로 연결된다. (J2SE 환경 기준)
“엔티티를 영구 저장하는 환경”이라는 뜻이다.
하지만 DB에 직접 저장하는 공간이 아니라 ‘논리적인 메모리 공간’ 이다.
눈에 보이지 않으며, 엔티티 매니저를 통해서 접근한다.
EntityManager.persist(entity);
여기서 persist()는 DB에 저장하는 게 아니라
영속성 컨텍스트에 엔티티를 저장하는 것이다.
💡 “영속화”는 DB 저장이 아니라 ‘영속성 컨텍스트가 관리하기 시작한 상태’라는 뜻이다.
상태 | 설명 |
---|---|
비영속 (new / transient) | 영속성 컨텍스트와 전혀 관계없는 상태 |
영속 (managed) | 영속성 컨텍스트에 의해 관리되는 상태 |
준영속 (detached) | 영속성 컨텍스트에서 분리된 상태 |
삭제 (removed) | 삭제된 상태 |
Member member = new Member();
member.setId("member1");
member.setUsername("회원");
단순히 객체만 생성된 상태 — 아직 영속성 컨텍스트와 아무 관련 없음.
em.persist(member);
이 시점에서도 DB에 저장된 건 아니다.
실제 DB에 반영되는 건 트랜잭션 커밋 시점이다.
tx.commit(); // 이때 INSERT 쿼리 실행
em.detach(member);
영속성 컨텍스트에서 해당 엔티티를 관리하지 않게 만든다.
이후 커밋해도 아무 쿼리도 나가지 않는다.
em.remove(member);
삭제 요청을 등록한 상태.
마찬가지로 실제 DELETE 쿼리는 커밋 시점에 실행된다.
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다.
여기엔 엔티티의 @Id를 key로, 엔티티 객체 자체를 value로 저장한다.
Member findMember1 = em.find(Member.class, "member1");
Member findMember2 = em.find(Member.class, "member1");
→ 첫 번째는 DB 조회 후 1차 캐시에 저장
→ 두 번째는 DB에 접근하지 않고 캐시에서 바로 반환
→ 따라서 findMember1 == findMember2 는 true
💬 캐시는 트랜잭션 단위로만 유지된다.
트랜잭션이 끝나면 캐시도 사라진다.
그래서 “성능상의 이점보다는 동작 메커니즘의 이점이 크다”
(즉, DB 트랜잭션과 객체 상태의 일관성을 유지할 수 있다.)
같은 트랜잭션 안에서는 같은 식별자를 가진 엔티티는 항상 같은 객체로 보장된다.
이는 데이터베이스의 트랜잭션 격리 수준 중 Repeatable Read를
JPA가 애플리케이션 레벨에서 보장해주는 효과다.
어렵게 생각하지 말고 “같은 트랜잭션 안에서는 같은 객체다” 정도로 이해하면 됨
JPA는 트랜잭션 커밋 전까지 SQL을 모아두었다가 한 번에 실행한다.
transaction.begin();
em.persist(member); // INSERT SQL이 아직 DB에 안감
transaction.commit(); // 이 시점에 flush + commit
persist(
는 INSERT SQL을 “쓰기 지연 SQL 저장소” 에 쌓아두고,
커밋 시점에 flush가 일어나며 SQL이 DB로 날아간다.
💡 이렇게 하면 버퍼링 / 배치 처리 최적화가 가능하다.
바로 SQL을 날리면 최적화 여지가 없기 때문.
Hibernate에는 hibernate.jdbc.batch_size 옵션으로 한 번에 보낼 수 있는 개수를 설정할 수도 있다.
Member member = em.find(Member.class, "member1");
member.setUsername("A");
tx.commit(); // UPDATE 쿼리 자동 실행
명시적으로 update()
를 호출하지 않아도 된다.
JPA는 커밋 시점에 엔티티의 스냅샷과 현재 상태를 비교해서 변경점을 감지한다.
“스냅샷(snapshot)” = 처음 조회했을 때의 원본 상태
차이가 생기면 UPDATE 쿼리를 생성해서 쓰기 지연 SQL 저장소에 넣는다
em.remove(member);
삭제 쿼리 역시 커밋 시점에 실행된다.
“영속성 컨텍스트의 변경 내용을 DB에 반영하는 과정”
즉, 쓰기 지연 SQL 저장소에 쌓여 있던 INSERT/UPDATE/DELETE 쿼리가 DB로 날아가는 시점이다.
플러시가 발생하면 순서대로
1. 변경 감지 (Dirty Checking)
2. 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
3. 저장소의 쿼리를 DB로 전송
플러시가 발생하는 시점
em.flush()
직접 호출💬 flush를 해도 1차 캐시는 비워지지 않는다.
flush vs commit 차이
flush는 “DB와 메모리 상태를 맞추는 과정”, commit은 “트랜잭션을 끝내는 단계”다.
옵션 | 설명 |
---|---|
FlushModeType.AUTO | 기본값. 커밋 또는 쿼리 실행 시 flush 자동 호출 |
FlushModeType.COMMIT | 커밋할 때만 flush 호출 |
👉 웬만하면 기본값 AUTO 그대로 두면 된다.
영속성 컨텍스트는 트랜잭션 단위로 설계되어야 한다.
트랜잭션이 끝나면 영속성 컨텍스트도 함께 종료되어야 데이터 동기화 문제를 피할 수 있다.
영속 상태의 엔티티를 영속성 컨텍스트에서 분리하면 준영속 상태가 된다.
→ 더 이상 JPA의 관리 대상이 아니며, 커밋해도 아무 일도 일어나지 않는다.
em.detach(entity); // 특정 엔티티만 분리
em.clear(); // 컨텍스트 전체 초기화
em.close(); // 컨텍스트 종료
깊게 이해는 지금 필요 없음.
영속 → 준영속으로 상태가 바뀌면 JPA가 관리하지 않는다 정도로 이해하면 된다.
JPA는 내부적으로 리플렉션을 사용해 객체를 동적으로 생성하기 때문에 기본 생성자(default constructor) 가 반드시 필요하다.
┌────────────────────────────────────────────┐
│ 엔티티 생명주기 │
└────────────────────────────────────────────┘
[비영속 (new/transient)]
└─ 객체만 생성된 상태
↓ em.persist(member)
[영속 (managed)]
└─ 영속성 컨텍스트가 관리하는 상태
↓ em.detach(member)
[준영속 (detached)]
└─ 영속성 컨텍스트에서 분리된 상태 (더 이상 관리 X)
↓ em.remove(member)
[삭제 (removed)]
└─ 삭제 요청된 상태 (commit 시 DELETE 쿼리 실행)
┌────────────────────────────────────────────────────┐
│ 트랜잭션 내 JPA 동작 흐름 │
└────────────────────────────────────────────────────┘
[1] transaction.begin()
↓
[2] em.persist(entity)
└─ INSERT SQL 생성 → 쓰기 지연 SQL 저장소에 저장
↓
[3] 변경 감지 (Dirty Checking)
└─ 엔티티 ↔ 스냅샷 비교 → UPDATE SQL 생성
↓
[4] em.flush() (자동 호출 or 직접 호출)
└─ 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송
↓
[5] transaction.commit()
└─ flush 자동 호출 → DB 반영 확정 (commit)
↓
[6] 트랜잭션 종료 → 영속성 컨텍스트 소멸
개념 | 핵심 요약 |
---|---|
영속성 컨텍스트 | 엔티티를 관리하는 메모리 공간 |
persist() | DB 저장이 아니라 영속성 컨텍스트에 등록 |
1차 캐시 | 트랜잭션 단위 캐시 (성능보단 일관성 유지 목적) |
쓰기 지연 | SQL을 모아두었다가 커밋 시 한 번에 실행 |
변경 감지 | 스냅샷과 현재 상태 비교 후 자동 update |
flush() | 변경 내용 DB 반영 (1차 캐시 유지됨) |
commit() | 트랜잭션 종료 및 확정 |
detach/clear/close | 영속성 컨텍스트에서 엔티티 분리 |
JPA의 핵심은 “언제 DB와 동기화되는가”를 이해하는 것이다.
영속성 컨텍스트는 단순한 캐시가 아니라, 트랜잭션 범위 내에서 객체 상태를 추적하고 변경을 관리하는 일종의 ‘미니 데이터베이스’다.
그래서 em.persist()나 member.setName()처럼 단순한 코드가 실제 SQL을 자동으로 만들어내는 것이다.