[JPA] 영속성 컨텍스트
자바 객체와 관계형 데이터베이스를 어떻게 매핑해서 사용해야 할까?
실제로 JPA는 내부적으로 어떻게 동작하는걸까?
JPA의 작동 원리를 제대로 이해하기 위해서는 영속성 컨텍스트에 대해 이해해야 한다.
영속성 컨텍스트는 엔티티를 영구적으로 저장하는 환경으로, 눈에 보이지 않는 논리적인 개념이다.
EntityManager를 생성하면 이에 대응하는 PersistenceContext 가 생성된다.
엔티티를 처음 만들면 엔티티는 비영속 상태이다.
persist 메서드를 사용하면 엔티티가 영속 상태가 되어 영속성 컨텍스트에 관리된다.
즉, em.persist() 메서드는 단순히 데이터베이스에 값을 저장하는게 아니고, 엔티티를 영속화함을 의미한다.
그런데 왜 이런 매커니즘을 도입했을까?
객체와 데이터베이스 사이에 영속성 컨텍스트 개념이 도입됐다.
이렇게 중간에 위치한 경우 여러 이점을 가질 수 있다.
1. 1차 캐시
데이터베이스의 PK로 매핑한 값을 key 값으로 사용하고 value는 엔티티 객체이다.
em.setId(1);
em.persist(member);
위의 코드를 실행한 경우 왼쪽과 같이 캐시가 저장된다.
em.find(Member.class, 1); 으로 조회 시 JPA는 먼저 1차 캐시를 찾아 해당하는 key값이 존재하는지 확인한다.
없으면 데이터베이스에서 존재하는지 확인하고, 데이터베이스에서 가져온 값을 1차 캐시에 저장한 후 값을 반환한다.
물론 고객의 요청 단위로 EntityManager를 만들어서 사용하기 때문에, 고객의 요청이 끝나는 경우 1차 캐시도 모두 삭제된다.
1차 캐시를 사용한다고 해서 성능이 크게 향상되지는 않지만, 영속 엔티티를 보장할 수 있다는 장점을 얻을 수 있다.
예를 들면 == 비교로 객체의 동등성을 보장할 수 있다던지..
2. 엔티티 등록
em.persist(member) 를 실행한다고 해서 바로 데이터베이스에 sql을 날리지 않는다.
트랜잭션을 커밋 하는 순간 데이터베이스에 sql이 전송된다.
em.persist(memberA) 를 실행하면 1차 캐시에 값이 저장됨과 동시에 JPA가 엔티티를 분석해 insert 쿼리를 생성한다.
생성된 쿼리는 쓰기 지연 SQL 저장소에 저장된다.
em.persist(memberB) 를 실행하면 1차 캐시에 값이 저장됨과 동시에 JPA가 엔티티를 분석해 insert 쿼리를 생성한다.
생성된 쿼리는 쓰기 지연 SQL 저장소에 저장된다.
트랜잭션을 커밋하게 되면 쓰기 지연 SQL 저장소에 저장된 쿼리들이 flush 돼 데이터베이스에 날아가고, 실제 데이터베이스에 반영된다.
이 방법을 사용하면 sql을 한 번에 보낼 수 있어 성능이 향상된다.
MyBatis같은 매퍼를 사용할 때 보다 성능을 더 향상시킬 수 있다.
3. 변경 감지
find 메서드로 가져온 값을 setter로 변경하면 따로 update 메서드를 호출하지 않아도 데이터베이스에 값이 갱신된다.
1차 캐시에는 아이디 / 엔티티 / 스냅샷이 존재하고, 트랜잭션을 커밋할 때 엔티티와 스냅샷을 비교한다.
스냅샷에는 값을 처음 읽어왔을 때의 값이 저장돼있다.
이 때 전송되는 쿼리는 SQL의 모든 필드를 업데이트 하는 쿼리인데..
DB 엔진에서 쿼리를 재사용하거나 미리 쿼리를 생성해 둘 수 있는 이점 덕분에 이 방법을 사용하긴 하지만, 업데이트 할 필드가 적어 성능 저하 우려가 있는 경우 하이버네이트의 확장 기능을 사용해 원하는 필드만 업데이트 할 수 있다.
비교해서 스냅샷과 엔티티 값이 다른 경우 업데이트 sql을 생성해 쓰기 지연 sql 저장소에 저장한다.
flush는 영속성 컨텍스트의 변경 내역을 데이터베이스에 반영하는 작업이다.
우선 EntityManager의 persist merge remove 등 메서드를 사용하려면 트랜잭션이 먼저 시작돼야 한다.
명시적으로 EntityManager에서 getTransaction 메서드를 통해 EntityTransaction 객체를 얻을 수 있고, 이 객체를 통해 트랜잭션을 시작, 커밋, 롤백 할 수 있다. (자바에서 데이터베이스의 트랜잭션을 다룬다고 생각하자)
flush가 발생하는 경우는 아래와 같다.
1. em.flush를 명시적으로 호출
2. 트랜잭션을 커밋
3. JPQL 실행
JPQL을 실행할 때는 결과가 최신 상태를 반영해야 하기에 플러시를 자동으로 수행한다.
플러시가 발생해도 1차 캐시는 지워지지 않고, EntityManager가 종료되거나 clear 메서드가 호출될 때 지워진다.
엔티티가 영속성 컨텍스트로 영속화됐다가 분리된 경우 해당 엔티티는 준영속상태이고, 영속성 컨텍스트가 관리한 적이 없는 엔티티는 비영속상태이다.
준영속 엔티티는 이전에 영속 상태였기에 ID 값을 가진다.
준영속 엔티티를 다시 영속 상태로 만들 때는 merge 메서드를 사용하는데, merge 메서드가 다루는 엔티티가 영속 상태인 경우 해당 엔티티를 업데이트하고 준영속 상태인 경우 해당 엔티티를 다시 영속 상태로 복귀시킨다.
'Spring > JPA' 카테고리의 다른 글
[JPA] 상속관계 매핑 (0) | 2022.12.23 |
---|---|
[JPA] 다양한 연관관계 매핑 (0) | 2022.12.22 |
[JPA] 연관관계 매핑 (0) | 2022.12.22 |
[JPA] 객체와 테이블 매핑 (0) | 2022.12.22 |
[JPA] 도입 (0) | 2022.12.21 |
댓글
이 글 공유하기
다른 글
-
[JPA] 다양한 연관관계 매핑
[JPA] 다양한 연관관계 매핑
2022.12.22 -
[JPA] 연관관계 매핑
[JPA] 연관관계 매핑
2022.12.22 -
[JPA] 객체와 테이블 매핑
[JPA] 객체와 테이블 매핑
2022.12.22 -
[JPA] 도입
[JPA] 도입
2022.12.21