영속성 컨텍스트

ANN·2024년 12월 8일

JPA(TIL)

목록 보기
2/4
post-thumbnail

해당 내용은 인프런 김영한 강사님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'의 강의를 기반으로 작성했습니다.

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

📢 JPA에서 가장 중요한 2가지


  • 객체와 관계형 데이터베이스 매핑하기(Object Relational Mapping)

    정적인 요소
    DB를 어떻게 설계하고, 객체를 어떻게 설계하고, 어떻게 JPA로 매핑할 것인가?

  • 영속성 컨텍스트

    JPA가 내부에서 어떻게 동작하는가?

일단 JPA의 매커니즘을 이해해보자.

📢 엔티티 매니저 팩토리와 엔티티 매니저


엔티티 매니저 팩토리

  • 요청이 올 때마다 엔티티 매니저 생성

엔티티 매니저

  • 내부적으로 데이터베이스 커넥션을 생성하여 DB를 사용

여기까진 지난 번에도 학습한 내용이고...

📢 영속성 컨텍스트


"JPA를 이해하는 데 가장 중요한 용어"

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

즉, 이것을 "엔티티를 영속화" 한다고 하며,
이는 엔티티 객체를 영속성 컨텍스트라는 데에 저장하는 것

❓영속성 컨텍스트란?

  • 논리적인 개념 -> 눈에 보이지 않는다.
  • 또한, 엔티티 매니저를 통해 영속성 컨텍스트에 접근
EntityManager.persist(entity);

이것은 엔티티 객체를 DB에 저장하는 것 그 이상으로, 영속화하는 것

엔티티 매니저를 생성하면, 그 안에 1:1로 영속성 컨텍스트라는 눈에 보이지 않는 공간이 생김

📢 엔티티의 생명주기

❓비영속(new/transient)

영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

최초 멤버 객체를 생성한 것
: 멤버 객체는 생성했는데 아직 엔티티 매니저엔 넣지 않은 상태
= JPA와 관련 없음

// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

❓영속(managed)

영속성 컨텍스트에 관리되는 상태

엔티티 매니저를 얻어와서 엔티티 매니저(영속 컨텍스트)에 객체를 저장한 상태

// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

// 객체를 저장한 상태(영속)
em.persist(member);

위 그림이 된 거임 = 영속 상태

em.persist(member) : 이때 DB에 저장되는 게 아니다!
영속 상태가 될 뿐, 영속 상태가 된다고 해서 쿼리가 날아가는 게 아님
트랜잭션을 커밋하는 시점에 쿼리가 날아감

❓준영속(detached)

영속성 컨텍스트에 저장되었다가 분리된 상태

// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);

영속성 컨텍스트에서 member 객체 분리

❓삭제(removed)

삭제된 상태

// 객체를 삭제한 상태
em.remove(member);

📢 영속성 컨텍스트의 이점

애플리케이션과 데이터베이스 사이의 중간 계층이 있음
-> 버퍼링을 할 수도 있고, 캐싱을 할 수도 있고...

// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

// 객체를 저장한 상태(영속)
em.persist(member);

~영속된 상태~

❓1차 캐시

영속성 컨텍스트는 내부에 1차 캐시라는 걸 들고 있음

1차 캐시에서 조회

멤버 객체를 저장하고 조회를 하면, JPA는 DB보다 영속성 컨텍스트를 먼저 찾음
-> 캐시에 있는 값을 조회해 옴

Member findMember = em.find(Member.class, "member1");

데이터베이스에서 조회

  • id가 "member2"인 데이터는 1차 캐시에 없음
  • 1차 캐시를 찾아보고 없으면 DB를 조회
  • 해당 데이터를 1차 캐시에 저장
  • 엔티티 반환
Member findMember2 = em.find(Member.class, "member2");

🤔 그런데 이게 큰 이점은 아님

엔티티 매니저라는 건 "데이터베이스 트랜잭션 단위로 만들고 트랜잭션이 끝나면 종료"되기 때문에
1차 캐시가 굉장히 짧은 순간에만 이득이 있고,
여러 명, 여러 쓰레드가 사용하거나 공유하는 캐시가 아님(애초 엔티티 매니저가 여러 쓰레드가 공유할 수 없기도 하고...)

현업에서도 큰 도움을 주진 않지만,
이 컨셉이 주는 효과가 있다고 함

❓영속 엔티티의 동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true

마치 자바컬렉션에서 똑같은 레퍼런스가 있는 객체를 꺼내면 같은 인스턴스이듯이
1차 캐시된 객체를 꺼내오기 때문에 같은 객체다.

" 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공"

-> JPA에서 같은 트랜잭션 안에서 이렇게 실행하면, == 같은 비교 연산자 사용시 True가 나옴
애플리케이션에서도 트랜잭션 단위로 엔티티를 사용한다는 건가? 라고 생각해봄

❓엔티티 등록

쿼리는 persist 때 나가는 것이 아님
-> persist 때마다 DB에 쿼리를 날리면 최적화할 여지조차 없음

데이터베이스에 아무리 데이터를 넣어도 commit을 해야 유효하니,
commit 전에 쿼리를 날림

위 사진처럼 엔티티 매니저에 Member 1과 2를 쌓고 이걸 한 번에 보낼 수 있음(=JDBC배치)
-> 하이버네이트 옵션도 있음

위처럼 설정한 사이즈만큼 모아서 DB에 쿼리를 한 방에 보내고 `commit`함

이걸 실전에서 같은 걸 여러 개 DB에 저장하는 경우가 많지는 않은데,
중요한 건, JPA를 써서 성능을 먹고 들어갈 수 있다는 것
내가 생짜로 쿼리를 짜고,그 쿼리를 모으고, commit 전에 넣기는 너무 힘든데
이렇게 버퍼링을 모아서 write하기

어디서 어떻게 모아 저장할까?

(1) 영속성 컨텍스트의 쓰기 지연 SQL 저장소에 저장

각 객체를 persist하면 모두 쓰기 지연 SQL저장소에 저장

(2) DB에 flush & commit이 됨

애플리케이션에서 commit을 날리면 영속성 컨텍스트에서 DB로 flushcommit을 날림

❓엔티티 수정 - 변경감지

JPA의 목적은 자바 컬렉션 다루듯이 객체(엔티티)를 다루는 것
자바 객체를 수정 후 다시 컬렉션에 넣지 않듯, 엔티티도 그렇게!

위처럼 set함수로 값만 바꿨는데도 update쿼리가 나옴

실제로 값도 변경됨

이렇게 할 수 있는 이유가 바로 더티 체킹(Dirty Checking), 즉 변경 감지 기능 때문

🤔변경감지란?

JPA가 데이터베이스 트랜잭션을 commit하는 시점에, 내부적으로 flush라는 게 호출이 됨

  • 1차 캐시 안에는 ID와, Entity, 그리고 스냅샷이 있음

    스냅샷이란?

    내가 값을 읽어온 시점의 값을 떠놓은 것

  • 이 상태에서 내가 'Member1'값을 변경한 후 commit을 하면, 내부적으로 flush가 호출되고,

  • 이때 JPA가 스냅샷과 Entity를 비교하여,

  • 변경사항이 있는 경우 update 쿼리를 쓰기 지연 SQL 저장소에 저장

  • 이를 DB에 반영

❓엔티티 삭제

변경과 매커니즘이 같은데,
쿼리가 Delete쿼리임

📢 결론

  • 엔티티 변경에는 persist가 필요하지 않음
  • "엔티티의 데이터가 변경되면 업데이트를 하고 안 되면 안 할 거야" 같은 코드가 필요하지 않고 update 쿼리가 날아감
if (member.getName().equals("ZZZzzz")) {
	em.update(member);
}
  • 위와 같은 코드 불필요
  • JPA는 값을 바꾸면 트랜잭션이 commit되는 시점에 변경을 반영한다고 생각하고 코드를 작성하자

0개의 댓글