본문 바로가기
카테고리 없음

[JPA] 3장 - 영속성 관리

by niahh 2025. 5. 23.

3.2 영속성 컨텍스트란? 

 

영속성 컨텍스트 = 엔티티를 영구 저장하는 환경

엔티티 매니저로 엔티티 저장, 조회하면 엔티티매니저가 영속성 컨텍스트에 엔티티 보관, 관리함. 

 

em.persist(member);

엔티티 매니저를 사용해 회원 엔티티를 영속성컨텍스트에 저장한다는 뜻. 

 

영속성 컨텍스트는 엔티티 매니저를 생성할때 하나 만들어짐. 

엔티티 매니저를 통해 영속성 컨텍스트에 접근하고, 영속성 컨텍스트를 관리할 수 있음. 

 

3.3 엔티티 생명 주기 

 

엔티티에는 4가지 상태가 존재한다. 

비영속 : 영속성 컨텍스트와 전혀 관계 없는 상태

영속 : 영속성 컨텍스트에 저장된 상태

준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제 :  삭제된 상태

 

비영속

엔티티 객체를 생성했지만 영속성 컨텍스트에는 저장하지 않았다. 

 

Member member = new Member()

member.setId("member1")

member.setUserName("user1")

 

영속 

엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장한 상태. 

 

준영속 

영속성 컨테스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다. 

em.detach()를 통해 준영속 상태로 만든다. 

 

삭제 

엔티티를 영속성 컨텍스트와 데이터베이스 모두에서 삭제한다. 

em.remove(member);

 

영속성 컨텍스트의 특징 

1. 영속 상태는 식별자 값이 반드시 있어야 함

2. 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장되는 엔티티를 데이터베이스에  저장함(flush)

3. 영속성 컨텍스트가 엔티티를 관리하면 장점이 있다 

- 1차 캐시 

- 동일성 보장

- 트랜잭션을 지원하는 쓰기 지연

- 변경 감지

- 지연 로딩

 

엔티티 조회

영속성 컨텍스트 내부의 캐시를 1차 캐시라 한다. 

내부 MAP에 키는 @ID로 매핑한 식별자, 갑승ㄴ 엔티티 인스턴스로 저장된다. 

 

1차 캐시에서 조회 

EM.FIND()

우선 1차 캐시ㅣ에서 식별자 값으로 엔티티를 찾음. 

엔티티가 있으면 디비를 조회하지 ㅇ낳고 메모리에 있는 1차 캐시에 엔티티를 조회함. 

만약 1차 캐시에 없으면 데이터 베이스에서 조회한다. 

 

데이터베이스에서 조회

엔티티가 1차 캐시에 없으면 엔티티 매니저는 데이터베이스를 조회하여 엔티티를 생성한다.

 

여기더 정리??

 

영속 엔티티의 동일성 보장 

식별자가 같은 엔티티 인스턴스를 조회해서 비교하기 

 

엔티티 등록

엔티티 매니저를 사용해 엔티티를 영속성 컨텍스트에 등록한다. 

쓰기지연: 엔티티 매니저가 insert sql문을 내부 쿼리 저장소에 모아놓고, 트랜잭션을 커밋할때 한꺼번에 쿼리를 디비로 보내는 것. 

 

쓰기 지연이 가능한 이유 

 

엔티티 수정 

sql 수정 쿼리의 문제점 

sql을 사용하면 수정 쿼리를 직접 작성한다.

프로젝트가 커지고 요구사항이 늘어나면 수정 쿼리도 커진다. 

회원의 이름과 나이를 변경하는 sql문을 보자. 

update member set name = ?, age = where id = ?

 

여기에서 회원의 등급을 변경하는 기능이 추가되면 회원의 동급을 변경하는 다음 수정 쿼리를 추가로 작성한다. 

추가로 작성할수도있고, 두개의 쿼리를 합쳐 하나의 쿼리만 사용할수도 있다. 

update member set name = ?, age = ?, grade = ? where id = ?

 

여기서의 문제점: 수정 쿼리가 많아지고, 비즈니스 로직을 분석하기 위해 sql 을 확인해야 하는 상황이다. 

비즈니스 로직이 sql에 의존하게 된다. 

 

변경감지 

JPA는 엔티티를 어떻게 수정할까?

memberA.setUsername("alice");

memberA.setAge(10);

이처럼 JPA로 엔티티를 수정할때는 엔티티를 조회해여, 데이터만 변경하면 된다. 

엔티티의 데이터만 변경해도 디비에 반영이 된다! 

 

이런기능을 '변경감지'라고 한다. 

 

JPA가 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장한다. 이것을 '스냅샷'이라 한다. 

flush 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다. 

 

순서대로 분석해보면, 

1. 트랜잭션을 커밋하면, 엔티티 매니저 내부에서 flush()호출됨 

2. 엔티티와 스냅샷을 비교해 변경된 엔티티를 찾음. 

3. 변경된 엔티티가 있으면 수정 쿼리를 생성하여, 쓰기 지연 sql 저장소에 보냄

4. 쓰기지연 저장소의 sql문을 디비에 보냄 

5. 디비 트랜잭션을 커밋함. 

 

이 변경감지 기능은 영속 상태의 엔티티에만 적용된다. 

영속성 컨텍스트의 관리를 받지 못하는 상태에선 적용되지 않는다. 

 

JPA의 기본 적략은 엔티티의 모든 필드를 업데이트 하는것이다. (수정된 데이터만 반영하는것이 아니라) 

update member 

set name = ?, age = ?, grade = ?, ... where id = ?

 

디비에 보내는 데이터 전송량이 증가하는 단점 존재함. 

하지만, 장점은:

- 모든 필드를 사용하면 수정 쿼리가 항상 같다. (바인딩 되는 데이터는 다름). 

그래서 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용 가능함. 

- 디비에 동일한 쿼리를 보내면 디비는 이전에 한 번 파싱된 쿼리를 재사용 가능함. 

 

만약, 필드가 너무 많거나 저장되는 내용이 너무 크면 동적으로 update sql을 생성하면 된다. 

이때는 하이버네이트 확장기능을 사용해야 한다. 

@org.hibernate.annotations.DynamicUpdate 를 사용하면 된다. 

(필요하면 더 찾아보기)

 

엔티티 삭제

엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다. 

Member memberA = em.find(Member.class, "memberA")

em.remove(memberA)

 

플러시 

플러시는 영속성 컨텍스트의 변경 내용을 디비에 반영한다. 

 

구체적인 동작 순서는

1. 변경 감지가 동작하여 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교하여, 수정된 엔티티를 찾는다. 

2. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 sql 저장소에 등록한다. 

 

플러시하는 방법은 3가지

1. em.flush() 직접 호출 

2. 트랜잭션 커밋 -> 플러시 자동 호출됨 

3. JPQL 쿼리 실행 -> 플러시 자동 호출됨 

 

플러시 모드 옵션 

 

준영속 

1. 엔티티를 준영속 상태로 전환: detach() 

2. 영속성 컨텍스트 초기화: clear()

3. 영속성 컨텍스트 종료: clsoe() 

 

준영속 상태 특징 

1. 거의 비영속 상태에 가깝다 

2. 식별자 값 가짐 

3. 지연 로딩 할 수 없음. 

 

병합 

1. 준영속 병합 

2. 비영속 병합