일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Database
- JPA
- 트랜잭션
- PostgreSQL
- ArrayList 가변
- 스키마 자동 생성
- Container
- 자바 동시성
- 컨테이너
- ArrayList 소스코드
- JPA란
- index
- thread safety
- postgres
- acid
- create-drop
- 멀티스레드 싱글톤
- load balancer
- 도커
- 싱글톤 동시성
- heap
- 데이터베이스
- ArrayList 길이 확장
- 권장 PK 전략
- JPA 장점
- github
- java
- 로드밸런서
- docker
- transaction
- Today
- Total
JS
[JPA #2] 영속성 컨텍스트 (Persistence Context) 본문
JPA의 Persistence Context(영속성 컨텍스트)란 엔티티를 영구 저장하는 환경입니다.
JPA는 엔티티 관리를 위해 여러가지 기능을 제공하는데 그 기반이 되는 것이 바로 영속성 컨텍스트 입니다. (가장 중요)
JPA는 엔티티를 데이터베이스가 아닌 엔티티 매니저를 통해 접근 가능한 특정 환경에 저장한 뒤 사용합니다.
이를 이해하기 위해서는 엔티티의 생명주기에 대해 알아야 합니다.
엔티티의 생명주기
엔티티의 생명주기에는 비영속, 영속, 준영속, 삭제 이렇게 4가지가 있습니다.
- 비영속 (new/transient)
비영속 상태란, 엔티티 객체를 생성은 했지만 영속성 컨텍스트와는 관계가 전혀 없는 완전히 새로운 상태를 뜻합니다.
- 영속 (managed)
생성한 엔티티 객체를 생성한 뒤 엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태를 뜻합니다.
엔티티 매니저의 persist
메서드를 통해 객체를 영속성 컨텍스트에 등록할 수 있습니다.
객체를 새로 생성할 때도 영속 상태를 만들어줄 수 있지만, 이미 데이터베이스에 존재하는 데이터의 경우에도 영속 상태를 만들어줄 수 있습니다.
특정 객체 조회 시 영속 상태가 아니라면 DB 조회를 통해 1차 캐시에 등록해서 영속 상태를 만들어줍니다. (1차 캐시에 대한 부분은 아래에서 조금 더 자세히 알아보겠습니다.)
영속 상태가 된다고 해서 DB 쿼리가 바로 날라가는 것이 아니고 트랜잭션의 커밋 시점에 DB 쿼리가 날라갑니다.
- 준영속 (detached)
영속성 컨텍스트를 통해 관리되고 있는 엔티티 객체가 분리된 상태를 뜻합니다.
준영속 상태가 되면 영속성 컨텍스트가 제공하는 기능들을 사용할 수 없기 때문에 거의 사용할 일이 없습니다.
- 삭제 (removed)
이름 그대로 삭제된 상태를 뜻합니다.
영속성 컨텍스트의 이점
영속성 컨텍스트는 어떤 이점들을 제공하는지 알아보겠습니다.
1차 캐시
영속성 컨텍스트 내부에는 1차 캐시가 존재합니다.
객체가 영속상태가 되면 1차 캐시에 key, value 형태로 등록됩니다.
여기서 key는 pk로 맵핑한 값이고, 값은 저장한 엔티티 객체 자체가 됩니다.
JPA는 조회 시 DB 조회에 앞서 1차 캐시를 먼저 확인하기 때문에, 1차 캐시에 조회하고자 하는 엔티티 객체가 존재한다면 DB까지 갈 필요없이 1차 캐시에서 해당 객체를 가져올 수 있습니다.
이 과정에서 만약 조회 시 1차 캐시에 원하는 엔티티 객체가 없다면, DB에서 가져와 1차 캐시에 저장 후 반환합니다.
이후에 같은 객체 조회 시 1차 캐시에 저장한 객체를 반환하게 되는 것이죠.
일반적으로 우리가 알고 있는 캐시의 개념과 같습니다.
하지만 이 1차 캐시라는 것은 하나의 DB 트랜잭션 단위 동안만 살아 있습니다. 고객의 요청 들어와서 비즈니스 로직이 전부 수행된 후 끝나버리면 사라지게 됩니다.
그래서 동시요청이 많을 때의 큰 성능 개선 효과는 기대하기는 어렵습니다.
영속 엔티티의 동일성 보장
JPA는 같은 트랜잭션에서 조회한 엔티티를 다시 조회할 경우, 캐시(1차 캐시)를 통해 불러오기 때문에 몇 번을 호출하던 추가 SQL문 호출 없이도 같은 엔티티임을 보장해줍니다.
1차 캐시로 REPEATABLE READ 격리 수준을 애플리케이션 레벨에서 제공해주는 셈이죠.
User user1 = em.find(User.class, 1L);
User user2 = em.find(User.class, 1L);
// 동일성 보장 (true)
System.out.println(user1 == user2);
트랜잭션을 지원하는 쓰기 지연
영속성 컨텍스트에는 1차 캐시 말고도 쓰기 지연 SQL 저장소라는 것이 존재합니다.
쓰기 지연 저장소는 SQL문들을 들고 있다가 트랜잭션 커밋 시점에 한번에 DB로 처리하기 위해 사용됩니다.
INSERT 문을 각각 따로 호출하는 것과, 모아서 batch insert 하는 것의 차이인 것이죠.
엔티티 객체를 persist 하면, 1차 캐시에 저장됨과 동시에 해당하는 INSERT 문이 쓰기 지연 저장소에 등록됩니다.
만약 여기서 다른 엔티티 객체를 추가로 persist 하면, 똑같이 1차 캐시에 추가되고 쓰기 지연 저장소에 INSERT 문이 추가됩니다.
그리고 최종적으로 트랜잭션 커밋 시점이 오면 쓰기 지연 저장소에 모아두었던 SQL문들을 한번에 DB로 flush 하게 됩니다.
실제 SQL문들은 트랜잭션 커밋 시점에 한번에 날라가게 되는 것이죠.
그렇다면 한번에 몇개의 SQL문을 처리할 수 있느냐에 대한 것은 아래와 같이 batch_size
속성 설정을 통해 관리할 수 있습니다.
<property name="hibernate.jdbc.batch_size" value="10"/>
변경 감지 (Dirty Checking)
JPA의 가장 큰 장점 중 하나는 마치 자바 컬렉션을 다루듯이 객체를 다룰 수 있다는 부분입니다.
컬렉션에 포함된 객체의 필드 값을 변경할 때는 값 변경 이후 다시 컬렉션에 넣어줄 필요가 없습니다. 객체의 참조값을 가지고 있기 때문에 굳이 다시 저장해주는 과정을 거칠 필요가 없기 때문이죠.
JPA에서도 마찬가지로 엔티티 객체의 값을 변경할 때, 값을 변경한 뒤 다시 persist 해줄 필요가 없습니다.
자바 컬렉션에서 하는 것 처럼 setter를 통해 값을 변경하면 해당 변경 사항을 알아서 감지합니다.
JPA는 이런 dirty checking을 어떻게 가능하게 할까요?
1차 캐시에는 key, entity와 최초 읽어들였을 당시 스냅샷이 함께 포함되어 있습니다.
이때 트랜잭션 커밋에 의한 flush가 발생하면 엔티티와 스냅샷을 비교하여 변경된 값이 있는지 확인합니다.
만약 변경된 내용이 있다면 UPDATE 쿼리를 쓰기 지연 저장소에 추가합니다.
그런 뒤 트랜잭션이 커밋되는 시점에 쓰기 지연 저장소에 있던 쿼리들과 같이 flush 합니다.
이런 dirty checking 기능이 제공되기 때문에 개발자가 직접 UPDATE 문의 필요 유무를 굳이 코드로 확인할 필요가 없어지게 되는 것이죠. (실수가 줄어드는 효과도 있겠죠?)
Flush
사실 위에서 flush에 대한 이야기를 이미 하고 있어서 flush에 대한 내용이 먼저 왔어야 좋지 않았나 싶지만...
flush란 영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 기능입니다.
flush가 발생하면 dirty checking을 진행하고, dirty checking의 결과에 따라 수정된 엔티티에 대한 SQL문을 쓰기 지연 저장소에 등록합니다. 그리고 최종적으로 쓰기 지연 저장소에 있는 쿼리들을 데이터베이스에 보냅니다.
기본적으로 flush는 트랜잭션 커밋 시점에 자동으로 실행되지만, 다른 방법들도 존재합니다.
실제로 이렇게 할 일은 드물지만 em.flush()
를 통해 직접 flush 할 수 있습니다.
만약 persist만 한 상태에서 쿼리를 직접 확인하고 싶은 경우에 사용할 수 있습니다.
추가로, flush는 JPQL 쿼리를 실행할 시 자동으로 호출됩니다.
정리하자면,
flush는 영속성 컨텍스트에 어떤 변경사항을 일으키지 않습니다.
트랜잭션 커밋 이후에는 영속성 컨텍스트의 소멸로 인해 1차 캐시는 사라지게 되지만, flush 가 1차 캐시에 어떤 변경사항을 일으키는 것은 아니라는 뜻입니다.
flush는 영속성 컨텍스트의 변경내용을 DB에 동기화해주는 역할만 하는 기능입니다.
준영속 상태
위의 영속성 컨텍스트 생명주기를 소개하는 부분에서 간단하게 언급했지만,
준영속 상태는 기존에 영속성 컨텍스트로 관리되던 엔티티 객체를 분리해서 더 이상 영속성 컨텍스트에 의해 관리되지 않는 상태를 뜻합니다.
준영속 상태로 만드는 방법은 다음과 같습니다.
- em.detach(entity): 특정 엔티티만 준영속 상태로 전환
- em.clear(): 영속성 컨텍스트 초기화
- em.close(): 영속성 컨텍스트 종료
세 방법 중 어떤 방식을 사용하더라도 준영속 상태라면 위에서 정리한 영속성 컨텍스트의 이점들을 이용할 수 없게 됩니다.
이상으로 JPA의 핵심인 영속성 컨텍스트에 대한 정리를 마칩니다.
'JPA' 카테고리의 다른 글
[JPA #5] 연관관계 매핑 (Relationship Mapping) (0) | 2023.01.03 |
---|---|
[JPA #4] 엔티티 매핑 (Entity Mapping) (0) | 2022.10.28 |
[JPA #3] 데이터베이스 스키마 자동 생성 기능 (0) | 2022.10.28 |
[JPA #1] JPA란? JPA를 왜 사용할까? JPA 장점은? (0) | 2022.10.27 |