JPA 내부구조

2021-02-04

JPA를 공부하면서 정리를 한 내용들 입니다.

-참고 김영한 개발자님 : [토크ON세미나] JPA 프로그래밍 기본기 다지기 - JPA 내부구조 T아카데미

6강. JPA 내부구조

1.JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기

  • 영속성 컨텍스트(PersistenceContext)

2.영속성 컨텍스트란?

  • JPA를 이해하는데 가장 중요한 용어
  • 엔티티를 영구 저장하는 환경이라는 뜻
  • EntityManager.persist(entity);
  • 영속성 컨텍스트는 논리적인 개념(눈에 보이지 않습니다.)
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근
  • J2SE 환경에서는 엔티티 매니저와 영속성 컨텍스트가 1:1
  • J2EE, 스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1
  • 같은 트랜잭션이면 같은 영속성 컨텍스트에 접근하게 됩니다.

Java SE(Standard Edition) : 자바 스탠다드 에디션은 가장 보편적으로 쓰이는 자바 API집합체입니다.

Java EE(Enterprise Edition) : 자바 엔터프라이즈 에디션은 자바를 이용한 서버측 개발을 위한 플랫폼입니다. Java EE는 표준 플랫폼인 Java SE를 사용하는 서버를 위한 플랫폼입니다.

3.엔티티의 생명주기

비영속(new/transient)

  • 영속성 컨텍스트와 전혀 관계가 없는 상태
  • Member 객체를 생성만 한 상태
  //객체를 생성한 상태(비영속)
  Member member = new Member();
  member.setId("memberId");
  member.setUsername("회원1");

영속(managed)

  • 영속성 컨텍스트에 저장된 상태
  • 객체를 생성하고 저장한 상태(영속성 컨텍스트에 의해서 객체가 관리(managed)되는 상태)
  Member member = new Member();
  member.setId("memberId");
  member.setUsername("회원1");
  
  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();
  
  //객체를 저장한 상태(영속)
  em.persist(member);

준영속(detached)

  • 영속성 컨텍스트에 저장되었다가 분리 (관리를 포기한) 상태
  • em.detach(member);

삭제(removed)

  • 삭제된 상태
  • em.remove(member);

4.영속성 컨텍스트의 이점

왜 이렇게 중간에 영속성 컨텍스트라는게 존재하는가?

Member를 쿼리를 통해서 DB에 넣어버리면 되지,

왜 중간상태가 있어야하는가?라고 생각할 수 있습니다.

그렇기에 이것에 대한 이 점은 아래와 같습니다.

1차 캐시

PersistenceContext에는 내부에 1차 캐시가 있습니다.

일반적인 캐시가 아니라 영속성컨텍스가 생성되고 없어질 때 까지만 잠깐 존재하는 메모리입니다.

  • 1차 캐시에서 조회를 하면?
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

//1차 캐시에 저장됌
em.persist(member);

//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

em.find()에서 DB로 바로가는 것이 아니라 1차 캐시를 먼저 탐색합니다.

  • 존재하면 바로 반환

  • 1차 캐시에 없으면 데이터베이스에서 조회하고
    조회된 내용을 1차 캐시에 저장 후에 반환합니다.

영속 엔티티의 동일성(identity) 보장

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

System.out.println(a == b); // 동일성 비교 true (1차캐시가 있기 때문에)

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을

데이터베이스가 아닌 어플리케이션 차원에서 제공합니다.

트랜잭션을 지원하는 쓰기 지연(버퍼 기능)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

em.persist(memberA);
em.persist(memberB);

transaction.commit(); // 커밋
  • persist(memberA) 명령의 경우 memberA1차 캐시에 저장하면서
    동시에 INSERT SQL을 생성해서 쓰기 지연 SQL 저장소에 말아둡니다.

  • 이후에 persist(memberB) 명령의 경우도 위와 같이 동작하고, 아직 DB에 넣지 않습니다.

  • 이후에 commit() 명령을 해야 쓰기 지연 SQL 저장소에 있던
    INSERT SQL 쿼리 2개를(옵션에 따라 동시에 혹은 하나씩) DB에 넣습니다.

  • 쓰기 지연 SQL 저장소에 있던 쿼리들을 날리는 과정을 flush라고 합니다.

    • 하지만 flush를 한다고 1차캐시의 내용들이 지워지는 것이 아니라 쿼리를 보내서 DB와 싱크를 맞추는 역할을 합니다.
    • commit()flushcommit 두 가지 일을 합니다.

변경 감지(Dirty Checking)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setName("hjs");
memberA.setAge(10);

// em.update(member) 이런 코드가 있어야 하지 않을까?

transaction.commit(); // [트랜잭션] 커밋

왜 이렇게 되는가?

JPA는 트랜잭션이 커밋되는 시점에 1차 캐시 뿐만 아니라 스냅샷도 생성합니다.

commit()명령으로 flush를 하면 영속성 컨텍스트에 의해 관리되는 엔티티들을

스냅샷과 비교해서 바뀐 부분이 있으면 UPDATE 쿼리를 만들어서 DB에 보내고 commit을 합니다.

이렇게 하는 이유

Java 컬렉션에서 값을 가져와서 변경해도 다시 컬렉션에 값을 담지 않습니다.

그래도 컬렉션의 값이 바뀝니다. 그것과 똑같은 컨셉입니다.

마치 Java 컬렉션에서 값을 가져와서 변경하는 것 처럼하기 위해 이런 방식으로 처리합니다.