일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 실행 컨텍스트
- JPA
- 정처기
- 스프링 부트
- 다이나믹프로그래밍
- 깃허브
- MongoDB
- 자바의 정석
- NoSQL
- 분할정복
- spring security
- 이벤트루프
- 캐시
- 레디스
- 정보처리기사
- 가상 면접 사례로 배우는 대규모 시스템 설계 기초
- 동적계획법
- SQL
- 스프링부트
- sqld
- github
- 영속성 컨텍스트
- 호이스팅
- VMware
- 스프링 시큐리티
- Redis
- in-memory
- 게시판
- document database
- Spring Boot
- Today
- Total
FreeHand
JPA 영속성 컨텍스트 본문
시작하며
자바 백엔드 개발자 공고를 찾아보면 대부분 공통적으로 원하는 기술 스택이 있습니다. RDB, NoSQL, Docker, MSA, AWS 등 요구하는 기술 중 가장 기본은 아무래도 Spring Boot + JPA 조합인 것 같습니다. DB에 접근해서 데이터를 다루는 작업은 모든 서비스에서 필수인만큼 JPA를 사용하는 회사라면 당연히 JPA에 대한 이해도가 중요할 것 같습니다. 그래서 이번에는 JPA의 기본 동작 원리인 영속성 컨텍스트에 대해서 정리해보려고 합니다.
영속성 컨텍스트
JPA에는 마치 스프링 컨테이너와 같이 눈에 보이지 않는 논리적 개념인 영속성 컨텍스트가 존재합니다. EntityManager를 통해 Entity를 영속성 컨텍스트에서 관리함으로써 Entity를 영속화하기 위함입니다.
Entity는 영속성 컨택스트와의 관계에 따라 다음과 같은 생명주기를 갖습니다.
- 비영속 상태(new)
- 영속성 컨텍스트와 관련이 없는 상태
- new 키워드로 엔티티 객체를 생성만 한 상태
- 영속 상태(managed)
- 영속성 컨텍스트에서 관리되는 상태
- persist() 메서드를 통해 영속성 컨텍스트에서 관리되는 상태
- 준영속 상태(detached)
- 영속성 컨텍스트에서 분리된 상태
- 삭제 상태(removed)
- 삭제된 상태
영속성 컨텍스트의 특징
영속성 컨텍스트는 다음과 같은 주요 특징이 있습니다.
- 1차 캐시
- 쓰기 지연
- 변경 감지(Dirty Checking)
하나씩 더 자세히 알아보겠습니다.
쓰기 지연
쓰기 지연을 쉽게 설명하자면 "실제 DB에 insert 쿼리가 실행되는 시점은 persist를 했을 때가 아닌 commit을 했을 때이다."라고 할 수 있습니다. 코드를 보겠습니다.
// 생략
Member member = new Member();
member.setId(1L);
member.setName("Jin");
System.out.println("Before persist");
entityManager.persist(member); // 1차 캐시에 저장
System.out.println("After persist");
transaction.commit(); // 실제 DB에 저장
"After persist" 이후에 insert 쿼리가 실행되었습니다. 이것을 통해 insert 쿼리가 실행되어 실제로 DB에 저장되는 시점은 persist 메서드가 아닌 commit 메서드가 실행될 때라는 것을 확인할 수 있습니다.
영속성 컨택스트에는 SQL 저장소가 있는데 이곳에 insert 쿼리가 저장되어 있다가 트랜잭션을 commit을 하면 모아둔 SQL을 DB에 보내 실행합니다.
1차 캐시
앞의 내용에서 commit 이후에 insert 쿼리가 실행되는 것을 확인했습니다. 그럼 persist 메서드는 무엇일까요.
영속성 컨텍스트에는 1차 캐시가 존재하는데, 캐시라는 이름대로 DB에 접근하기 전에 먼저 확인되는 곳입니다. 1차 캐시를 이해하기 위해 코드를 보겠습니다.
// 생략
Member member = new Member();
member.setId(1L);
member.setName("Jin");
entityManager.persist(member); // 1차 캐시에 저장
Member findMember = entityManager.find(member.class, 1L); // id가 1인 member 조회
System.out.println("name = " + findMember.getName());
transaction.commit(); // 실제 쿼리 실행
select 쿼리가 실행되지 않고 바로 name을 출력했습니다.
find 메서드로 조회할 때 1차 캐시를 먼저 확인하기 때문에 select 쿼리를 통해 DB에서 조회하기 전에 1차 캐시에서 id가 1인 member를 조회한 것입니다.
지금처럼 1차 캐시를 확인해서 있으면 가져오고 없으면 DB에서 1차 캐시로 가져오고 그 엔티티를 다시 반환합니다.
그럼 member 인스턴스는 언제 1차 캐시에 저장되었을까요.
"entityManager.persist(member);" 바로 이 코드가 member 인스턴스를 1차 캐시에 저장한 것입니다.
즉 persist() 메서드는 DB에 저장하는 것이 아니라 영속성 컨택스트의 1차 캐시에 저장하는 메서드입니다.
그리고 이렇게 1차 캐시에 저장된 상태를 영속 상태라고 합니다.
변경 감지
엔티티에 변경이 생기면 자동으로 update 쿼리가 실행되는 것을 변경 감지라고 합니다.
엔티티가 영속성 컨텍스트에 저장될 때 최초의 상태를 복사해서 저장해 두는데 이를 스냅샷이라고 합니다.
1차 캐시에서 스냅샷과 비교해서 변경이 있으면 쓰기 지연 SQL 저장소에 update 쿼리를 저장했다가 flush 합니다.
따라서 변경 감지는 영속 상태인 엔티티만 적용되는 개념입니다.
플러시: 영속성 컨텍스트의 변경내용을 DB에 반영
마치며
JPA의 기본 동작 원리인 영속성 컨텍스트에 대해서 알아봤습니다.
Spring Data JPA를 사용하다 보면 영속성 관리를 신경 쓰지 않고 사용하다 보니 JPA의 기본적인 내용도 모르고 사용하게 되는 경향이 있습니다. 간편하게 사용할 수 있다고 해도 어느 정도는 기초 내용을 알고 사용하는 것이 바람직하다고 생각합니다.