도입
지난 포스팅에선 JPA를 활용하여 데이터를 저장하고 삭제하고 변경하는 작업을 해보았다. JPA를 사용하여 CRUD를 진행할 때 어떠한 원리로 동작을 하게 되는지 굉장히 많이 궁금하였다. 이번 포스팅에서는 JPA의 작동원리인 영속성 컨텍스트에 대하여 알아보려고 한다.
영속성 컨텍스트?
영속성 컨텍스트란 엔티티를 영구히 저장하는 환경으로 JPA의 저장공간이라고 생각하면 된다. 객체를 생성하고 JPA를 활용하여 DB에 넣어주게되면, 쿼리로 바로 DB에 넣어주는 것이 아니라 영속성 컨텍스트라는 공간에 따로 저장을 해두는 것이다. 해당 트렌잭션이 끝날 때(commit) 그 동안 모아둔 쿼리를 DB에 쏴주게 된다. 여기서 중요한 것은 영속성 컨텍스트는 당연히 하나의 트렌젝션에서만 공유가 된다.
언제 영속성 컨텍스트의 내부 구조
영속성 컨텍스트의 내부구조는 SQL을 저장하는 저장공간과 객체들을 저장하는 공간으로 볼 수 있다. 영속성 컨텍스트의 특징으로 트랜젝션을 수행하며 DB에 날려야하는 쿼리를 모아두었다가 날린다고 하였다. SQL의 저장공간에는 DB에 날려주어야 하는 쿼리들을 저장해두고 있다. 그리고 영속성 컨텍스트로 관리되고 있는 객체에 대한 정보를 저장해 둔다. 이 때 스냅샷은 최초 객체가 생성될 때의 상태를 저장해두며 이것을 저장하는 이유는 추후 Dirty Check를 위해서이다.
영속성 컨텍스트의 생명주기
영속성 컨텍스트는 비영속, 영속, 준영속, 삭제상태 4가지 생명주기를 갖는다.
비영속
비영속은 객체만 생성된 경우를 의미한다. 즉 엔티티 매니저가 아직 persist()로 객체를 부르지 않은 상태이다.
Hello hello = new Hello(); //비영속 상태의 객체
아직 영속이 되지 않은 상태이므로, 영속성 컨텍스트에서 관리하고 있지 않다.
영속
엔티티 매니저에 의해 영속상태로 변경될 수 있다. persist()를 사용하여 객체를 영속성 컨텍스트에서 관리하도록 할 수 있다.
Hello hello = new Hello(); //비영속 상태의 객체
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(hello); //영속 상태로 변경되는 객체
tx.commit();
영속성 컨텍스트로 변경을 할 때 항상 트랜잭션 안에서 수행이 되어야 한다. 이렇게 새롭게 persist를 하는 경우에 영속 상태가 될 수 있고 아니면 DB에서 sql로 처음 호출 될 때 영속 상태로 변경된다.
준영속
준영속은 영속성 상태였다가 분리가 되는 경우이다. 즉 영속성 컨텍스트에서 관리를 하다가 이제 관리를 안받게 된 경우이다.
영속 -> 준영속으로 가며 Entity Manager에 의해 분리될 수 있다.
em.detach(hello);
em.clear();
em.detach로 영속성 컨텍스트에서 파라미터로 넘겨준 객체를 제외시켜 준영속으로 변경시킬 수 있고 clear를 사용하여 영속성 컨텍스트를 모두 날릴 수 있다. 이렇게 영속성 컨텍스트에서 분리되어 준영속 상태가 되면 그 객체는 영속성 컨텍스트의 기능을 사용할 수 없게 된다.
삭제
em.remove(hello);
삭제상태는 Entity Manager에 의해 remove()로 호출되어 DB에서 삭제되는 상태를 의미한다. 영속성 컨텍스트의 DB로 쿼리를 넘기는 순간은 remove()명령이 떨어지고 바로가 아니라, 트렌젝션이 커밋이 되는 경우(플러시가 발생하는 순간)이므로 아직 DB에는 데이터가 남아있는 상태이다.
영속성 컨텍스트의 장점
영속성 컨텍스트로 저장공간을 두면 매우 많은 이점이 생기게 된다.
- 1차 캐시
- Dirty Check
- 동일성 보장
- SQL 쓰기지연
- 지연로딩
지금부터 영속성 컨텍스트의 장점에 대하여 알아보도록 하겠다.
1차 캐시
1차 캐시가 가능해진다. 영속성 컨텍스트에 있는 정보를 사용함으로써 DB에 쿼리를 날리지 않고 사용 가능하기 때문이다. 하지만 크게 프로그램의 성능을 개선시켜주지는 못한다. 왜냐하면 사용자마다 각자의 영속성 컨텍스트를 가지고 있고, 1차 캐시를 하기 이전에 영속성 컨텍스트로 관리하기 위해서는 어짜피 DB를 한번 가서 찾아오긴 해야한다. 그런데, 보통 개발을 할 때 DB에서 동일한 객체를 가져오는 일은 드물기 때문이다. 따라서 큰 효과는 없다.
Dirty Check
Dirty Check가 가능해진다. 영속성 컨텍스트에 객체가 들어갈 때 초기의 정보를 스냅샷으로 기억을 하고 있게 된다. 그리고 마지막에 트랜잭션이 종료되고 커밋이 되면서 저장된 스냅샷과 확인하여 객체의 정보가 바뀌었으면 Update SQL을 만들어 상태를 바꾸어준다. 지난 시간에 알아본 CRUD의 U가 이러한 원리로 실행되기 때문에 따로 persist를 하지 않아도 변경되었던 이유이다.
동일성 보장
영속성 컨텍스트에서 ID가 동일한 객체를 꺼낸 경우에는 동일성이 보장이 되어 == 비교를 하면 True의 값을 반환한다. 영속성 컨텍스트가 없이 DB에서 바로 값으로 가져온 경우라면 동일성이 보장되지 않지만, 영속성 컨텍스트에서 꺼내서 사용하기 때문에 동일성이 보장된다.
SQL 쓰기지연
SQL을 명령에 따라 바로 수행하지 않고 나중에 필요할 때 한번에 쓸수 있다.
지연로딩
지연로딩은 엔티티의 연관관계를 조회하는 시점이 아닌 엔티티를 참조하는 시점에 연관관계와 관련된 SQL을 질의하는 방법이다.
JPA에서 매우 중요한 부분이므로 추후 다른 포스팅에서 다루도록하겠다.(n+1 Select Problem 해결)
플러시
영속성 컨텍스트에서는 트랜잭션에 대한 SQL문을 모아서 한번에 보내준다고 하였다. 이러한 과정을 플러시라고 한다. 플러시는 트랜잭션이 종료될 때(Commit) 혹은 JPQL이 호출될 때 Entity Manager에 의해 flush()로 호출 될 때 수행된다.
Entity Manager로 직접 flush()를 호출할 수 있는데 이 경우 영속성 컨텍스트에서 관리하고 있는 객체들은 사라지지 않는다.
JPQL이 호출 될 때 실행이 되는 이유는 JPQL은 쿼리로 어쩔수 없이 DB에 보내주어야하는 상황이기 때문에 JPQL 호출 상황에도 플러시가 일어난다.
이 글은 김영한님의 강의 "자바 ORM 표준 JPA 프로그래밍"를 공부한 후 작성한 글 입니다.
'Back-end > JPA' 카테고리의 다른 글
JPA 기본 키 매핑하기 (0) | 2022.01.12 |
---|---|
JPA로 필드와 칼럼 매핑하는 annotation 알아보기 (0) | 2022.01.10 |
JPA로 객체와 테이블 매핑하기 (0) | 2022.01.07 |
JPA로 CRUD하기 (0) | 2021.12.30 |
JPA 프로젝트 설정(Maven, H2 DB) (0) | 2021.12.30 |