영속성은 어떻게 관리될까?
영속성 컨텍스트 (Persistence Context)
--
영속성(Persistence)은
데이터를 프로그램이 종료되더라도 유지할 수 있도록 저장하는 개념으로
일반적으로 애플리케이션에서 중요한 데이터를 DB나 파일 시스템 같은 영구 저장소에 저장하여,
시스템이 재시작되거나 장애가 발생해도 데이터를 유지할 수 있도록 한다.
영속성 컨텍스트는
JPA에서 엔티티 객체를 관리하고, 트랜젝션이 끝나면 변경 사항을 DB에 반영한다.
굳이 해석하면 "엔티티를 영구 저장하는 환경"이다.
엔티티의 생명 주기
- 비영속 (new / Transient)
- 영속 (Persistent)
- 준영속 (Detached)
- 삭제 (Removed)
비영속과 준영속은 비슷해 보이지만
단 한 번이라도 영속성 컨텍스트에 관리된 적이 있는지 여부다.
1. 비영속 상태
Member member = new Member();
member.setId(1L);
member.setName("John");
- 객체만 생성한 상태 (영속성 컨텍스트와 연결되지 않은 상태 = JPA와 전혀 관계없는 상태)
- DB와 전혀 관련 없음
2. 영속 상태
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 영속 상태로 변경 (객체를 저장한 상태)
em.persist(member);
// DB에 반영(저장 or 변경사항 적용)
em.getTransaction().commit();
- 엔티티 매니저를 생성한 뒤에 해당 엔티티 매니저에 객체를 저장(persist) 또는 조회(find) 한 상태
- 엔티티 매니저 안에 존재하는 영속성 컨텍스트를 통해서 저장한 객체를 관리 (DB에 저장되는 것은 아님)
- 이후 트랜잭션 커밋을 하면 DB에 수행한 작업들이 반영
3. 준영속 상태, 삭제
// 준영속 상태로 변경 (영속성 컨텍스트에서 분리)
em.detach(member);
em.getTransaction().commit();
// 객체를 삭제 (DB에서 해당 객체(테이블) 데이터 삭제)
em.remove(member);
em.getTransaction().commit();
- 엔티티 매니저에서 "detach"키워드를 통해 영속성 컨텍스트에서 객체를 제외(분리)한다.
- 준영속 상태로 변경 시 더이상 변경 사항이 자동 반영되지 않음
- 엔티티 매니저에서 "remove"키워드를 통해 DB에 저장한 객체(테이블)를 삭제한다.
준영속 상태로 만드는 방법
1. em.detach(entity)
=> 특정 엔티티만 준영속 상태로 변경
2. em.clear()
=> 영속성 컨텍스트를 완전히 초기화
3. em.close()
=> 영속성 컨텍스트를 종료
--
애플리케이션과 DB 사이에 존재하는 "영속성 컨텍스트"의 이점
--
1. 1차 캐시
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 엔티티 생성 (비영속)
Member memberA = new Member();
member.setId("memberA");
member.setUsername("홍길동");
// memberA 엔티티 영속
em.persist(memberA);
- 엔티티를 영속하면 영속 컨텍스트의 1차 캐시에 저장된다.
- JPA가 조회할 때 DB보다 우선적으로 1차 캐시에서 조회 후 가져온다. (캐시에서 가져올 때는 SQL문 수행 X)
- 만약 1차 캐시에 조회할 데이터가 없다면 그때 DB에서 조회 후 1차 캐시에 저장한 뒤에 1차 캐시에서 가져온다.
장점
- 불필요한 DB 조회를 줄여 성능 향상
- 같은 트랜잭션 내에서 같은 객체를 재사용하여 매모리 효율성을 높임
영속성 컨텍스트는
트랜젝션이 끝나면 종료되므로 1차 캐시도 없어져서 큰 이점은 없다.
즉, 1차 캐시의 내용은 트랜잭션 동작 동안에만 유지된다.
(애플리케이션의 전체에서 공유하는 캐시는 JPA기준으로 2차 캐시라고 부른다.)
2. 영속성 엔티티의 "동익성" 보장
Member member1 = em.find(Member.class, "memberA");
Member member2 = em.find(Member.class, "memberA");
System.out.println(a == b);
// true
- 1차 캐시가 있기 때문에 같은 영속성 컨텍스트 내에서 동일한 엔티티 객체는 동일성을 보장한다.
(즉, 같은 데이터를 두 번 조회해도 처음 조회했던 객체와 동일한 객체가 반환된다.) - 불필요한 중복 객체 생성을 방지하여 메모리 절약
3. 엔티티 등록 트랜잭션을 지원하는 "쓰기 지연"
EntityManager em = emf.createEntityManager();
// 트랜잭션 시작
em.getTransaction().begin();
Member memberA = new Member();
memberA.setId("memberA");
memberA.setUsername("홍길동");
Member memberB = new Member();
memberB.setId("memberB");
memberB.setUsername("고길동");
em.persist(memberA); // INSERT문을 통해 저장소에 저장 (즉시 실행 X)
em.persist(memberB); // INSERT문을 통해 저장소에 저장 (즉시 실행 X)
// 트랜잭션 커밋 시 한 번에 INSERT문 실행
em.getTransaction().commit();
em.close();
flush() 명령어는
영속성 컨텍스트의 변경 내용을 DB에 즉시 반영하는 명령어로
쓰기 지연 SQL 저장소에 있는 SQL문들을 즉시 실행하는 역할을 한다.
해당 명령어는 직접 코드에 작성하지 않아도
- 트랜잭션 커밋 시
- JPQL 실행 시
- em.clear() 호출 시 (영속성 컨텍스트 초기화)
등 위와 같은 상황에 자동으로 호출된다.
- 트랜잭션 동작 중에는 DB에 SQL문을 전달하지 않고 "쓰기 지연" 저장소에 담아 둔다.
- 마지막에 트랜잭션을 커밋하는 순간 "쓰기 지연" 저장소에 담아둔 모든 SQL문을 한 번에 DB로 전달한다.
- SQL문을 한 번에 모아서 실행하므로 DB를 왔다 갔다 하는 불필요함을 줄여 성능을 최적화
(트랜잭션 단위로 처리하여 성능 최적화) (여러 SQL문을 한 번에 처리하여 DB 부화 감소)
4. 엔티티 변경 감지
EntityManager em = emf.createEntityManager();
// 트랜잭션 시작
em.getTransaction().begin();
// 영속 엔티티 조회
Member member = em.find(Member.class, 1L);
// 영속 엔티티 데이터 수정 (UPDATE 실행 X)
member.setUsername("둘리");
// 커밋 시 변경 감지 → 자동 UPDATE 실행
em.getTransaction().commit();
em.close();
1차 캐시에서
- Entity : 현재 엔티티 상태
- 스냅샷 : 엔티티가 처음 영속 상태에 들어갔을 때의 엔티티 상태를 저장 (값을 읽어온 시점의 데이터)
엔티티 값을 변경하면 따로 명시적으로 JPA를 호출하여 DB에 쿼리를 날려 적용하지 않아도
자동으로 UPDATE SQL 쿼리를 날려 DB에 값을 변경한다.
- 엔티티 값을 변경하면 1차 캐시에 변경 사항을 저장한다. (쓰기 지연 저장소에는 적용 X)
- 변경 후 트랜잭션 커밋(내부적으로 flush() 호출됨)을 하면
1. 1차 캐시의 Entity와 스냅샷을 일일이 전부 비교
2. 변경이 감지되면 UPDATE SQL쿼리를 생성하여 쓰기 지연 저장소에 담아둔다.
3. 동작을 모두 수행하면 담아둔 모든 쿼리를 DB에 반영 후 커밋된다.
--
flush() 명령어
--
트랜잭션에서 마지막에 커밋이 일어나면 자동으로 flush()가 호출되는데
해당 flush() 명령어가 영속성 컨텍스트의 변경 내용을 DB에 반영하는 역할을 수행한다.
flush() 호출시키는 방법
- em.flsuh()로 직접 호출
- 트랜잭션 커밋
- JPQL 쿼리 실행
flush() 동작 단계
- flush() 호출
- 변경 내용 감지
- 변경된 엔티티를 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리들을 DB에 전송
flush()에는 "모드" 옵션이 존재한다.
// ex
em.setFlushMode(FlushModeType.COMMIT);
기본값은 "FlushModeType.AUTO"로
커밋이나 쿼리를 실행할 때 flush()가 동작한다는 설정이고
"FlushModeType.COMMIT"은
커밋할 때만 flush()가 동작한다는 설정이다.
--
참고 및 출처
'JPA' 카테고리의 다른 글
상속관계 매핑 (+@MappedSuperclass) (1) | 2025.04.01 |
---|---|
연관관계 매핑 (단방향, 양방향, 1:1, 1:N, N:1, N:N) (0) | 2025.03.31 |
엔티티(Entity) 매핑 (= 객체와 DB 테이블 연결) (0) | 2025.03.30 |
JPA 설정하기 (Spring boot 기준) (0) | 2025.03.25 |
JPA 개념 (0) | 2023.12.30 |