03 영속성 컨텍스트
03 영속성 컨텍스트
1. Note
- 어렵다 어렵다 어렵다!!!
- 필요한 데이터를 최소한의 쿼리로 조회해서, 영속성 컨텍스트에 캐싱하고,
- 한번에 몰려서 플러쉬가 날라가지 않게 조절하는것이 JPA를 활용하는 방법인건가
2. 영속성 컨텍스트
1. 영속성 컨텍스트
- 엔티티(Entity)를 영구 저장하는 환경
- 실제로는 애플리케이션과 데이터베이스 사이에서 엔티티를 담아두고 관리하는 논리적인 공간(메모리 캐시)
- EntityManager를 통해 이 공간에 접근하고 관리
2. JPA의 영속성
- 영속 상태만 JPA가 관리
- 영속 상태에서만 변경 감지가 동작
- 트랜잭션이 끝나면 대부분 준영속 상태
- 준영속 상태에서는 값을 바꿔도 DB에 반영안됨!
3. 영속성의 4가지 상태
1. 흐름
2. 비영속 (Transient)
- 아직 JPA와 전혀 관계가 없는 상태
- 단순히 메모리에만 존재하는 일반 객체 / new로 만든 객체
- DB와 연결되지 않음
- 영속성 컨텍스트에도 없음
- 저장되기 전 단계
3. 영속 (Managed)
- 영속성 컨텍스트가 해당 객체를 관리하는 상태
- JPA를 사용하는 이유의 핵심이 되는 상태
- 1차 캐시에 저장됨
- 동일성 보장 (같은 객체 유지)
- 변경 감지(dirty checking) 동작
- 트랜잭션 커밋 시 DB 반영
4. 준영속 (Detached)
- 원래는 영속 상태였지만, 현재는 영속성 컨텍스트에서 분리된 상태
- 더 이상 JPA가 관리하지 않음
- 변경 감지 동작 안 함
- DB 자동 반영 안 됨
5.삭제 (Removed)
- 삭제하기로 예약된 상태
- 아직 DB에서 바로 삭제된 건 아님
- 트랜잭션이 끝나면 DELETE 실행됨
3. 영속성 컨텍스트의 이점
1. 1차 캐시 (조회 성능 최적화)
- 같은 트랜잭션 안에서 동일한 엔티티를 여러 번 조회하면 DB를 다시 조회X
- 한 번 조회 → 영속성 컨텍스트에 저장
- 이후 조회 → DB 안 가고 캐시에서 반환
- 불필요한 SELECT 쿼리를 줄여버림
2. 동일성 보장 (Identity Guarantee)
- 같은 ID로 조회한 엔티티는 항상 같은 객체처리
1 2 3 4
User a = em.find(User.class, 1L); User b = em.find(User.class, 1L); //a == b (true)!
3. 변경 감지 (Dirty Checking)
- 엔티티 값을 변경하면, JPA가 자동으로 UPDATE 쿼리를 생성
1
user.changeName("홍길동");
4. 쓰기 지연 (Write-behind)
- INSERT / UPDATE / DELETE 쿼리를 바로 실행하지 않고 모아뒀다가 한 번에 실행
- DB I/O 감소
- batch 처리 가능
- 성능 향상
- 하지만, 트랜잭션이 끝난후에 커밋이 한번에 몰아칠 수 있어서 조심은 해야함.
5. 트랜잭션 단위 데이터 일관성
- 트랜잭션 안에서는 항상 동일한 데이터를 보장
- 같은 객체를 바라보고 처리하기 때문에 userA의 초기값과 나중값이 다를 수는 있음.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 1. 유저 생성 User userA = new User("userA"); em.persist(userA); Long id = userA.getId(); // 2. 값 변경 (UPDATE 쿼리 아직 안나감) userA.changeName("updatedUserA"); // 3. 같은 엔티티 다시 조회 User userB = em.find(User.class, id); // 4. 비교 System.out.println(userA == userB); // true System.out.println(userB.getName()); // updatedUserA
4. Flush
1. flush
- 영속성 컨텍스트의 변경 내용을 DB에 반영(동기화)하는 것
- flush는 현재 트랜잭션 내부의 정합성을 맞추기 위한 것
- 같은 트랜잭션 안에서도 DB 기준으로 조회해야 하는 순간 하는 작업
2. flush와 Commit
| 구분 | flush | commit |
|---|---|---|
| 역할 | SQL 실행 | 트랜잭션 확정 |
| 롤백 가능 | 가능 | 불가능 |
| 시점 | 중간에도 가능 | 마지막 |
3. 타이밍
1. 트랜잭션 커밋 직전
1
2
3
4
@Transactional
public void test() {
user.changeName("A");
}
- 메서드 끝날 때 (commit 직전) 자동 flush
- Dirty Checking 실행
- SQL 생성 및 실행
- 그다음 commit
- 가장 기본 흐름
- 아무것도 안 해도 마지막에 한 번은 무조건 flush
2. JPQL 실행 직전
1
2
em.persist(user);
em.createQuery("select u from User u").getResultList();
- JPQL은 DB 기준으로 조회함
- DB 상태를 최신으로 맞춰야 정확한 결과가 나오는 경우에 Flush
3. 직접호출
1
em.flush();
- 개발자가 강제로 플러쉬 진행!
4. Memo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Transactional
public void example(EntityManager em) {
em.persist(user1); // SQL 안 나감
user1.changeName("A"); // 메모리 변경
// JPQL 조회 필요해서 flush 발생
em.createQuery("select u from User u").getResultList();
// 메소드 종료, 커밋 직전 → 다시 flush
// 플러쉬로 보낼 데이터가 없다면 별도 쿼리 X
// 플러쉬는 작업 흐름의 이름!
}
This post is licensed under CC BY 4.0 by the author.

