본문 바로가기
JPA/자바 ORM 표준 JPA 프로그래밍 - 기본편

[JPA] 영속성 컨텍스트의 특징과 장점

by hk27 2022. 2. 3.

안녕하세요.

영속성 컨텍스트의 특징과 장점에 대해서 알아보겠습니다.

지난 게시글에서 JPA의 영속성 관리와 영속성 컨텍스트의 개념을 알아보았습니다.

궁금하신 분은 아래 게시글을 참고해주세요.

[JPA] 영속성 관리

 

영속성 컨텍스트는 '엔티티를 영구 저장하는 환경'이자 application과 DB 사이의 중간계층이며, 영속성 컨텍스트에 들어온 객체는 JPA에 의해 관리됨을 알아보았습니다.

영속성 컨텍스트의 특징을 더 알아보겠습니다.

 

영속성 컨텍스트의 특징

1. 영속성 컨텍스트와 식별자 값 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분합니다.

따라서 영속 상태의 객체는 반드시 식별자 값을 가져야 합니다. 예를 들어서 아래 코드를 수행해도 데이터가 DB에 저장되지 않습니다. 

Member member = new Member();
//member.setId("memberA");
member.setUsername("회원1");
member.setAge(20);
        
em.persist(member);

두 번째 줄의 setId 주석을 풀어주면 DB에 멤버 데이터가 저장됩니다.

 

2. JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하고, 이를 플러시(flush)라고 합니다. 플러시는 게시글의 후반부에서 자세히 다루겠습니다.

 

영속성 컨텍스트의 장점

영속성 컨텍스트는 4개의 장점이 있습니다.

하나씩 알아봅시다.

 

1. 1차 캐시

영속성 컨텍스트는 데이터를 저장할 때 영속성 컨텍스트 내 1차 캐시에 저장합니다. 

또한 find로 데이터를 찾을 때도 1차 캐시를 먼저 찾고, 없으면 DB에 쿼리를 날려서 데이터를 찾습니다. 

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

em.persist(member);

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

 

데이터를 persist 하면 1차 캐시에 저장됩니다. 

 

find 메소드가 불리면 1차 캐시에서 먼저 같은 id 값을 갖는 객체가 있는지 찾고, 있으면 DB에 쿼리를 보내지 않고 바로 반환합니다. 

 

1차 캐시에 데이터가 없으면 DB에 조회하여 데이터를 찾고, 조회한 데이터를 1차 캐시에 저장합니다. 

 

 

2. 영속 엔티티의 동일성 보장

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

멤버 b는 1차 캐시에서 데이터를 가져오므로 a와 b는 같은 객체입니다.

id 값이 같은 두 객체가 동일한 객체라는 것은 자바의 철학과 유사합니다.

 

 

3. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

단어가 어려운데 SQL 쿼리를 커밋 시점에 flush 할 때 한 번에 날린다는 의미입니다.

아래 코드에서 persist를 하면서 만들어지는 Insert SQL 쿼리 2개는 commit 시점에 한 번에 전송됩니다.

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

transaction.commit();

 

만들어진 SQL은 쓰기 지연 SQL 저장소에 저장되어 있습니다. 

 

transaction을 커밋할 때 flush가 호출되어서 한 번에 쿼리를 날립니다. 

INSERT뿐만 아니라 UPDATE, DELETE도 커밋 시점에 한 번에 쿼리가 나갑니다.

 

 

4. 변경 감지

JPA는 영속성 컨텍스트에 등록된 엔티티의 데이터를 수정하면 update를 호출하지 않아도 자동으로 UPDATE 쿼리를 전송합니다. 

Member member = new Member();
//member.setId("memberA");
member.setUsername("회원1");
member.setAge(20);
        
em.persist(member);

Member memberA = em.find(Member.class, "memberA");
memberA.setUsername("hi");
memberA.setAge(10);

나이는 10, 이름은 hi로 잘 변경된 것을 확인할 수 있습니다.

이렇게 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경 감지(dirty checking)이라고 합니다.

변경 감지 기능의 과정을 알아봅시다.

1) 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시(flush())가 호출됩니다.

2) 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾습니다.

3) 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보냅니다.

4) 쓰기 지연 저장소의 SQL을 DB에 보냅니다.

5) 데이터베이스 트랜잭션을 커밋합니다.

 

 

플러시

플러시(flush())는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것입니다.

플러시를 수행하면 다음과 같은 일이 일어납니다.

1. 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾습니다. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록합니다.

2. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다(등록, 수정, 삭제 쿼리)

 

영속성 컨텍스트를 플러시 하는 방법은 3가지입니다. 

1. em.flush() 직접 호출

2. 트랜잭션 커밋 시 자동 호출

3. JPQL 쿼리 실행 시 자동 호출 

 

참고로 플러시를 해도 영속성 컨텍스트의 엔티티가 지워지지 않고, 1차 캐시 정보가 유지됩니다. 

1차 캐시의 엔티티를 지우려면 em.clear()를 호출해야 합니다. 

 

지금까지 알아본 엔티티 매니저와 영속성 컨텍스트는 매핑한 엔티티를 실제로 사용하는 동적인 부분입니다.

다음 게시글부터는 엔티티와 테이블을 어떻게 매핑하는지 설계에 해당하는 정적인 부분을 알아보겠습니다. 

 

 

인프런  '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고 자료

자바 ORM 표준 JPA 프로그래밍 - 기본편, 섹션 3. 영속성 관리 - 내부 동작 방식 https://www.inflearn.com/course/ORM-JPA-Basic

김영한, 자바 ORM 표준 JPA 프로그래밍, 에이콘출판(2015), pp.95-109.

 

댓글