[JPA] 영속성 컨텍스트

김용현·2023년 12월 18일
0

JPA

목록 보기
1/12

본 포스트는 김영한 님의 자바 ORM 표준 JPA 프로그래밍 강의를 토대로 작성하였습니다.

영속성 컨텍스트란❓

엔티티를 영구 저장하는 환경

위 그림처럼 jpa를 통해 DB에 저장할 때 EntityMangagerFactory에서 EntityManager 객체를 획득하여 em.persist(member) 처럼 저장했다.

여기서 em.persist(member)는 실제로 데이터베이스에 정보를 저장하는 것이 아닌 영속성 컨텍스트에 저장하는 것이다

다음 그림처럼 영속 컨텍스트는 내부적으로 hashMap(key, Entity) 형태로 Entity 객체들을 저장한다. 즉 @Entity가 붙은 Member 클래스 객체를 persist 함수를 호출해 영속시킨다면 영속 컨텍스트 HashMap에 (member.id, member) 형태로 들어가게 되는 것이다.

참고

비영속 상태

다음처럼 객체를 생성하고 persist 함수를 호출하지 않았다면 해당 객체는 DB와 아무런 관계가 없는 비 영속 상태이다.

  • 2023.12.19 추가
    비영속 상태라는 것은 persist를 호출하지 않거나, 호출했는데 pk로 쓸 값이 존재하지 않는 경우(예를 들어 member Entity에 Long id 어노테이션으로 @Id 밖에 없고, member.setId()를 호출하지 않은 경우)이다. 후자의 경우 persist를 호출해서 영속 컨텍스트에 넣으려고 했으나, pk로 쓸 값이 존재하지 않아서 넣을 수 없는 경우 그냥 비영속 상태로 남는다.

영속 상태


그러나 다음처럼 persist를 호출하는 순간 해당 객체는 영속 상태가 되어 영속성 컨텍스트에 저장된다.(식별자로 쓸 값이 존재한다면) 따라서 persist 함수를 호출한다고 db에 바로 쿼리가 날라가지 않는다.(db에 저장하는 것이 아니니까, 실제로는 트랜잭션 commit을 호출할 때 sql이 날라가게 된다.)

준영속 상태


준영속 상태는 영속성 컨텍스트에서 해당 객체가 분리된 상태를 말한다.

  • 2023.12.19 추가

만약 이 상태라면 order는 준영속 상태이다. 이유는 아직 persist를 호출하지 않아서 영속 상태가 되지는 않았지만, 식별자로 사용할 id 값이 entity 객체에 존재하기 때문이다.
즉, 언제든지 다시 영속상태가 될 수 있는 상태를 말한다.

영속성 컨텍스트의 이점 🧐

위에서 본 것처럼 JPA는 객체를 DB에 바로 저장하지 않고 중간에 영속성 컨텍스트라는 상태를 두어 중간 단계를 하나 더 만들어 놓았다. 이러한 구조가 가지는 이점에 대해 알아보자.

장점1. 1차 캐시


먼저 캐싱 기능을 이용할 수 있다는 것이다. 캐시라 하면 감이 올 것 같은데, 조회 시 캐시된 것들을 찾음으로써 성능을 높일 수 있다.


따라서 find를 수행할 때 먼저 영속 컨텍스트 안에 해당 객체가 존재하는지 먼저 확인한 후 없을 시 db에서 해당 객체를 조회해온다. 이 때 조회한 객체를 영속 컨텍스트에 저장한 뒤 이를 반환하여 추후에 같은 객체 접근 시 캐시 기능을 활용할 수 있도록 한다.

참고❗️
그러나 이 1차 캐시는 실제로 큰 성능 상승을 얻기에는 어려움이 있다. entityManager는 한 트랜잭션 안에서 즉, 고객의 요청이 들어왔을 때 entityManager를 획득하고 트랜잭션을 실행시켜 db와 관련된 일을 수행하는데, 이 순간은 정말 짧은 순간이기 때문에 시스템 전체적으로 성능을 향상시키기에는 무리가 있다.

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

Member member1 = em.find(Member.class, 1L);
			Member member2 = em.find(Member.class, 1L);
			if (member1 == member2) {
				System.out.println("member1과 member2는 동일하다");
			}

다음과 같은 코드를 실행할 시 member1과 member2는 동일한 객체이다. 따라서 동일하다는 문장이 출력된다. 이처럼 앞선 1차 캐시 개념에서 알 수 있듯이 한 트랜잭션 내에서 같은 객체 접근 시 동일한 객체를 반환해준다.

장점3. 엔티티 등록 트랜잭션을 지원하는 쓰기 지연


위 사진처럼 commit하는 순간 쌓아놓은 쿼리를 한 번에 보낸다. 이해를 위해 아래 그림을 보자.


다음 그림처럼 persist를 호출하면 영속 컨텍스트에는 1차 캐시 말고도 쓰기 지연 sql 저장소라는 공간이 또 있다. 이 곳에 persist한 객체를 토대로 insert 쿼리를 생성하여 저장소에 저장한다. 그리고 transaction.commit()이 호출되면 flush 기능을 호출하여 쿼리들을 실행한다.

왜 이렇게 할까?
-> 버퍼 기능을 사용할 수 있기 때문이다. I/O를 하는 것보단 메모리에서 데이터를 가져오는 것이 훨씬 빠르기 때문인 것 같다.

추가적으로 batch 사이즈를 설정하여 모아놓는 쿼리 개수를 지정할 수도 있다. 자세한 건 검색!

장점4. 엔티티 수정 변경 감지

JPA는 update 함수가 없다. 대신 객체의 값을 변경하면 알아서 DB에 변경된 값을 집어넣어준다. 따라서 위 코드의 경우 member1.setName 이후 em.persist(member1)을 호출하여 영속 컨텍스트에 변경 사항을 저장하지 않더라도 JPA가 알아서 변경사항을 감지하고 sql을 날린다.

어떻게 가능할까?

다음 그림을 보면 사실 1차 캐시에는 pk와 Entity 말고도 SnapShot이라는 필드가 존재한다. 이 곳에 DB에서 객체를 가져오는 최초 시점의 객체 정보들을 저장해놓고 있다. 따라서 이 스냅샷 정보를 가지고 변경사항을 감지하는데 순서는 다음과 같다.

  1. 값 변경 이후 트랜잭션 commit을 호출한다.
  2. Entity에 저장된 객체와 SnapShot의 객체 정보들을 비교한다.
  3. 변경 사항이 있을 시 적절한 update 쿼리를 작성하여 쓰기 지연 sql 저장소에 저장한다.
  4. flush를 호출하여 쌓아놓은 쿼리문을 날린다.

따라서 개발자가 따로 update 사항을 알려주지 않더라도 JPA가 알아서 수정해준다.

flush(플러쉬)

flush가 호출되면 일어나는 일
1. 변경 감지
2. 수정된 엔티티 쓰기 지연 sql에 등록
3. 쓰기 지연 sql 저장소의 쿼리를 데이터베이스에 전송

참고❗️
flush는 쓰기 지연 sql 저장소에 있는 sql들, 변경 사항을 감지해서 sql을 만들어서 DB로 전송하는 역할을 한다. 따라서 영속 컨텍스트에 있는 1차 캐시의 값들은 지워지거나 하지는 않는다.

엔티티 생명주기

📚 참고자료

영속 컨텍스트에 관하여

profile
평생 여행 다니는게 꿈 💭 👊 😁 🏋️‍♀️ 🦦 🔥

0개의 댓글