HoonK212 GitHub_Blog

[JPA] JPA 기초 1

JPA 기초 1

JPA란?

  • Java Persistence API
  • 자바 진영의 ORM 기술 표준
    • ORM : Object-relational mapping(객체 관계 매핑)

JPA를 왜 사용해야 하는가?

  • 생산성
    • CRUD가 다 정의되어 있어서 편리함
      • 저장: jpa.persist(member)
      • 조회: Member member = jpa.find(memberId)
      • 수정: member.setName(“변경할 이름”)
      • 삭제: jpa.remove(member)
  • 패러다임의 불일치 해결
    • 객체는 상속관계가 있지만, 관계형 데이터베이스는 상속 관계가 없다.
    • 객체는 참조(reference)를 가지고 있다.
    • 관계형 데이터베이스는 PK나 FK로 join을 해서 필요한 데이터를 찾을 수 있다.

JPA의 동작

  • 2022-11-20-JPA-01.png
  • 저장
    • 2022-11-20-JPA-02.png
    • JPA에게 Member객체를 넘기면 JPA가 객체를 분석
    • INSERT query 생성 (JPA가 query 생성)
    • JPA가 JDBC API를 사용해서 INSERT query를 DB에 보냄
    • 패러다임의 불일치 해결
  • 조회
    • 2022-11-20-JPA-03.png
    • JPA에게 PK값으로 find 요청
    • JPA는 SELECT query 생성
    • JDBC API를 통해 DB에 보내고 결과를 받음
    • ResultSet 매핑
    • 패러다임 불일치 해결

JPA의 성능 최적화 기능

  • 1차 캐시와 동일성(identity) 보장
    • 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
    • DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장
  • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
    • 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
    • JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
  • 지연 로딩 (lazy loading)
    • 지연 로딩: 객체가 실제 사용될 때 로딩
    • 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회

엔티티 매니저 팩토리 & 엔티티 매니저

  • 2022-11-20-JPA-04.png
  • 엔티티 매니저 팩토리
    • 엔티티 매니저를 만드는 공장
    • 엔티티 매니저 팩토리는 생성하는 비용이 커서 한 개만 만들어 애플리케이션 전체에서 공유
    • 여러 스레드가 동시에 접근해도 안전
  • 엔티티 매니저
    • 엔티티의 CRUD 등 엔티티와 관련된 모든 일을 처리
    • 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안됨

영속성 컨텍스트란?

  • 엔티티를 영구 저장하는 환경
  • ex) EntityManger.persist(member);
    • 이 코드는 member 엔티티를 저장한다. 하지만 정확하게 얘기하면 데이터베이스에 저장하는게 아니라 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장하는 코드다. (트렌잭션이 commit하는 시점에 영속성 컨텍스트에 있는 엔티티들에 대한 쿼리가 실행된다.)
  • 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나만 만들어진다. 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있다.

엔티티의 생명주기

  • 2022-11-20-JPA-05.png
  • 비영속(new/transient)
    • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
    • 엔티티 객체가 생성된 순수 객체 상태
    • 아직 영속성 컨텍스트나 데이터베이스와는 전혀 관계가 없는 상태
  • 2022-11-20-JPA-06.png
  • 영속(managed)
    • 영속성 컨텍스트에 관리되는 상태
    • 엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장하면, 영속성 컨텍스트가 엔티티를 관리하므로 영속 상태가 된다.
  • 2022-11-20-JPA-07.png
  • 준영속(detached)
    • 영속성 컨텍스트에 저장되었다가 분리된 상태
    • 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 됨
    • 엔티티를 준영속 상태로 만드는 방법
      • EntityManager.detach(entity);
        • 엔티티를 준영속 상태로 만든다.
        • 영속성 컨텍스트가 지원하는 어떤 기능도 동작하지 않는다.
      • EntityManager.close();
        • 영속성 컨텍스트를 닫는다.
        • 해당 영속성 컨텍스트가 관리하던 영속 상태의 모든 엔티티가 준영속 상태가 된다.
      • EntityManager.clear();
        • 영속성 컨텍스트를 초기화한다.
        • 초기화된 수정 사항은 변경 감지가 동작하지 않는다.
  • 삭제(removed)
    • 삭제된 상태
    • 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
    • ex) EntityManager.remove(entity);

영속성 컨텍스트의 특징

  • 영속성 컨텍스트와 식별자 값
    • 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
  • 영속성 컨텍스트와 데이터베이스 저장
    • JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다. 이를 플러시(flush)라고 한다.
  • 영속성 컨텍스트가 엔티티를 관리하면 얻게되는 장점

    • 1차 캐시

      • 영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라 한다. 영속 상태의 엔티티는 모두 이곳에 저장된다. 쉽게 말해 영속성 컨텍스트 내부에 Map이 하나 있는데, 키는 @Id로 매핑한 식별자고 값은 엔티티 인스턴스다.
      • 엔티티를 조회하는 find 메서드 실행 순서
        1. 1차 캐시에서 식별자 값으로 엔티티를 찾는다.
        2. 만약 찾는 엔티티가 1차 캐시에 있으면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티를 조회
        3. 1차 캐시에 찾는 엔티티가 없으면 데이터베이스에서 조회
      • 1차 캐시는 한 트랜잭션 안에서만 효과가 있기 때문에 성능상 이점이 크지는 않다.
      • 동일성 보장

        Member a = em.find(Member.class, "member1");
        Member b = em.find(Member.class, "member1");
        
        System.out.println(a == b);
        
        • 위 코드의 a == b 는 참이다.
        • 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환하기 때문에 참이다. 따라서 영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다.
      • 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        // 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다
        transaction.begin(); // 트랜잭션 시작
        
        em.persist(memberA);
        em.persist(memberB);
        // 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
        
        transaction.commit(); // 트랜잭션 커밋
        
        • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보내서 저장시킨다. 이를 ‘transactional write-behind’라 한다.
        • 코드가 실행되는 과정
        1. em.persist(memberA)가 실행되면, 영속성 컨텍스트는 1차 캐시에 memberA에 대한 엔티티를 저장하면서 동시에 JPA가 이 entity를 분석해서 쓰기 지연 SQL 저장소에 INSERT 쿼리를 저장한다.
          • 2022-11-20-JPA-08.png
        2. em.persist(memberB)가 실행되면, memberA와 같은 작업이 실행된다.
          • 2022-11-20-JPA-09.png
        3. 트랜잭션을 commit하면, 엔티티 매니저는 영속성 컨텍스트를 플러시(flush) 한다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데 이때 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다.
          • 2022-11-20-JPA-10.png
      • 변경 감지(Dirty Checking)

        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin() // 트랜잭션 시작
        
        // 영속 엔티티 조회
        Member memberA = em.find(Member.class, "memberA");
        
        // 영속 엔티티 데이터 수정
        memberA.setUsername("hi");
        memberA.setAge(10);
        
        transaction.commit(); // 트랜잭션 커밋
        
        • 변경 감지란 엔티티의 변경사항을 DB에 자동으로 반영하는 기능
        1. 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시(flush())가 호출된다.
        2. 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
        3. 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
        4. 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
        5. 데이터베이스 트랜잭션을 커밋한다.
          • 2022-11-20-JPA-11.png
        • 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
        • 비영속, 준영속처럼 영속성 컨텍스트의 관리를 받지 못하는 엔티티는 값을 변경해도 DB에 반영되지 않는다.
        • JPA는 엔티티의 모든 필드를 업데이트 한다.
          • 장점 - 모든 필드를 사용하면 수정 쿼리가 항상 같다. 따라서 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있으며, DB에 동일한 쿼리를 보내면 DB는 이전에 한 번 파싱된 쿼리를 재사용할 수 있다.
          • 단점 - 데이터 전송량이 증가한다.
        • 지연 로딩(Lazy Loading)

“[JPA] JPA 기초” 시리즈는 인프런 김영한 강사님의 강좌를 기반으로 쓰여졌습니다.

(참고 : https://www.inflearn.com/users/@yh)