본문 바로가기

Backend/Spring

영속성 컨텍스트

JPA에서 가장 중요한 2가지는 다음과 같다.
- 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
- 영속성 컨텍스트
 
이제부터 영속성 컨텍스트에 대해서 조금 자세히 알아보려 한다.
 

영속성 컨텍스트

영속성 컨텍스트는 엔티티를 영구 저장하는 환경이라는 뜻이다.
이때, 엔티티는 데이터의 집합, 객체 정도로 생각하면 된다.
엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.
 

엔티티의 생명 주기

- 비영속
- 영속
- 준영속
- 삭제
 
비영속 상태영속성 컨텍스트와 전혀 관계가 없는 새로운 상태이다.
영속 상태영속성 컨텍스트에 의해 관리되는 상태이다.
준영속 상태영속성 컨텍스트에 저장되었다가 분리된 상태이다.
삭제 상태는 말 그대로 삭제된 상태이다.
 
아래 사진은 비영속 상태를 나타낸다.

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

비영속 상태는 위 예시 코드처럼 멤버 객체를 생성한 후, 엔티티 매니저에 아무것도 넣지 않은 상태를 나타낸다.
 
아래 사진은 영속 상태를 나타낸다.

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

em.persist(member);

위 예시로 member 객체의 상태 변화를 알아보자.
먼저, 객체를 생성한 당시에는 비영속 상태이다.
즉, em.persist(member) 코드 실행 전까지는 비영속 상태로 유지되다가, 해당 코드 실행 시 영속 상태가 된다.
멤버 객체 생성 후 엔티티 매니저를 통해 멤버 객체를 넣으면 엔티티 매니저 내부 멤버 객체가 들어가면서 영속 상태가 된다고 보면 된다.
 
하지만, 영속 상태가 된다고 해서 바로 DB에 쿼리가 날아가는 것은 아니니 주의하자.
 
이제 준영속, 삭제 상태에 대해서 알아보자.
 
아래 코드 중 하나를 실행하면 회원 엔티티를 영속성 컨텍스트에서 분리, 즉 준영속 상태로 만든다.

em.detach(member);
em.clear();
em.close();

em.detach(entity)의 경우 특정 엔티티만 준영속 상태로 전환하는 것이고, em.clear()는 영속성 컨텍스트를 완전히 초기화하는 것, 그리고 em.close()는 영속성 컨텍스트를 종료하는 것이다.
 
아래 코드를 실행하면 객체를 삭제한 상태, 즉 삭제 상태로 만든다.

em.remove(member);

 

영속성 컨텍스트의 이점

영속성 컨텍스트에는 다음과 같은 이점이 있다.
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
 

영속성 컨텍스트 내부에는 위 그림과 같이 1차 캐시가 있다.
 
아래 코드를 실행시키면 1차 캐시에서 회원 엔티티를 불러오게 된다.

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 1차 캐시에 저장됨
em.persist(member);

// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

 

영속성 컨텍스트는 엔티티를 찾을 때 DB에 먼저 접근하는 것이 아니라 1차 캐시에 먼저 접근해서 엔티티를 찾는다.
 
DB에서 엔티티를 조회하는 과정은 다음과 같다.

먼저, 찾으려는 엔티티가 1차 캐시에 있는지 없는지 확인해 보고, 있다면 1차 캐시에서 반환한다.
하지만, 없다면 DB에서 해당 엔티티를 찾고, 1차 캐시에도 저장해 준다.
이후에, 엔티티를 반환해 준다.
 
위에서도 언급했다시피, 영속성 컨텍스트는 동일성 보장을 해주는데, 아래 코드로 한 번 살펴보자.

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a == b);  // true

 
또한, 트랜잭션을 지원하는 쓰기 지연도 영속성 컨텍스트의 이점으로 언급한 바 있다.
코드로 한 번 살펴보자.

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

transaction.begin();

em.persist(memberA);
em.persist(memberB);

transaction.commit();

위에서 em.persist() 문까지만 실행한다고 해보면, INSERT SQL을 DB에 보내지 않는다.
하지만 아래에서 트랜잭션을 커밋하는 순간 DB에 INSERT SQL을 보내게 된다.
 

em.persist(memberA) 문을 실행하면 먼저 1차 캐시에 저장되고, 동시에 쓰기 지연 SQL 저장소에도 들어간다.
memberB도 같은 과정을 반복한다.
 

그러다가 트랜잭션을 커밋하는 시점에 쓰기 지연 SQL 저장소에 있던 것들이 flush되면서 쿼리가 날아간다.
이후, 실제 데이터베이스에 트랜잭션이 커밋된다.
 
이제 엔티티 수정에 대해서 알아보자.

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

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

memberA.setUsername("hi");
memberA.setAge(10);

transaction.commit();

회원 엔티티의 정보를 수정해주고 나서 em.update(member) 뭐 이런 코드가 있어야 할 거 같다.
하지만, 필요 없다.
그냥 값만 변경해주면 된다.
영속성 컨텍스트는 dirty checking, 즉 변경 감지 기능을 가지고 있다.
 

트랜잭션을 커밋하면 자동으로 flush가 호출된다.
이후, 엔티티와 스냅샷을 비교한다.
비교 후 변경된 부분이 있다면 update 쿼리를 쓰기 지연 SQL 저장소에 저장해 두고 데이터베이스에 이를 반영하고 커밋한다.
 
엔티티 삭제의 경우 아래와 같이 삭제 대상 엔티티 조회 후 삭제해 주면 된다.

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

em.remove(memberA);

 

flush

이전에 언급했던 flush에 대해서 조금 더 자세히 알아보자.
 
flush는 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것이다.
 
flush가 발생하는 경우는 다음 세 경우가 있다.
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송 (등록, 수정, 삭제 쿼리)
 
영속성 컨텍스트를 flush하는 방법은 다음과 같다.
- em.flush()
- 트랜잭션 커밋
- JPQL 쿼리 실행
그런데 em.flush()의 경우만 직접 호출하는 경우고, 나머지 두 경우는 자동 호출된다.
한편, flush를 해도 1차 캐시는 그대로 유지된다.
그냥 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 것이라고 보면 된다.
 
 
 

'Backend > Spring' 카테고리의 다른 글

다양한 연관 관계 매핑  (0) 2023.08.16
엔티티 매핑과 연관 관계 매핑  (3) 2023.08.09
빈 스코프  (1) 2023.08.03
빈 생명주기 콜백  (0) 2023.08.03
의존 관계 자동 주입  (0) 2023.08.03