JPA 스터디 (1) JPA 소개, 영속성 관리

Jihoon Oh·2022년 6월 16일
1

JPA 스터디

목록 보기
1/4
post-thumbnail

김영한님의 자바 ORM 표준 JPA 프로그래밍 강의와 책을 바탕으로 진행되는 스터디입니다

섹션 1. JPA 소개

SQL 중심적인 개발의 문제점

SQL 의존적인 개발을 피하기 어렵다.

  • 무한 반복, 지루한 코드
    • INSERT… UPDATE… SELECT… DELETE…
    • 자바 객체 → SQL, SQL → 자바 객체 무한 반복
  • 만약 객체에 필드가 추가되면?
    • 해당 객체를 사용하는 모든 CRUD의 SQL을 수정해주어야 한다

패러다임의 불일치

  • 객체를 저장하는 다양한 방법이 있지만 최선의 방법은 관계형 데이터베이스
  • 개발자가 SQL Mapper가 된다.
  • 객체와 관계형 데이터베이스의 차이가 많아 패러다임의 불일치로 오는 비용이 많음.
    1. 상속
      • 객체는 상속 관계를 가지고 있지만 테이블은 상속이 없다.
      • 그나마 슈퍼타입 - 서브타입 관계가 유사
        • 그러나 슈퍼타입 - 서브타입 관계를 사용하더라도 객체의 저장 시 SQL문을 두 개를 사용해야 함
        • 데이터를 조회해서 객체에 저장하려고 하면 JOIN문을 직접 작성해서 객체를 생성해야 함
        • 만약 자바 컬렉션에 데이터를 저장한다면 타입 고민 없이 그냥 컬렉션을 사용하면 됨
    2. 연관관계
      • 객체는 참조를 사용하고 테이블은 외래 키를 사용
        • 객체는 참조 방향으로만 조회가 가능(단방향)
        • 테이블은 외래 키 하나로 양방향 조회가 가능(MEMEBER JOIN TEAM / TEAM JOIN MEMBER)
      • 객체를 테이블에 맞춰서 모델링 할 경우 객체지향의 장점을 이용할 수 없음
        • 객체의 저장 / 조회는 편리함
        • 객체 참조를 활용 불가능
      • 객체지향적으로 모델링 할 경우 객체의 조회가 어려움
        • JOIN문을 사용해 연관된 필드를 조회하고 이를 매핑해주는 과정이 필요
      • 객체 그래프 탐색이 어려움
        • 객체 그래프 탐색: 객체에서 참조를 사용해서 연관관계를 탐색

        • SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해짐

        • 따라서 SQL문을 직접 보기 전 까지는 객체 그래프 탐색을 신뢰할 수 없음

          class MemberService {
              ...
              public void process() {
                  Member member = memberDAO.find(memberId);
                  member.getTeam(); // member -> team 객체 그래프 탐색이 가능?
                  member.getOrder().getDelivery(); // ???
              }
          }
        • 그렇다고 매번 모든 연관관계를 전부 로딩하기에는 비용이 많이 듦

        • 결국 상황에 따라 같은 객체에 대한 조회 쿼리를 여러 개 작성해야 함

      • 동일성 비교의 문제
        • 같은 SQL문을 사용해서 같은 트랜잭션에서 같은 객체를 조회하더라도 동일성 비교에는 실패함
    3. 데이터 타입
    4. 데이터 식별 방법

JPA 소개

JPA란?

  • Java Persistence API는 자바 진영의 ORM 표준 기술
    • ORM(Object-Relational Mapping)은 객체 - 관계형 데이터베이스 간 매핑을 해주는 프레임워크
  • JPA는 애플리케이션과 JDBC 사이에서 동작
  • SQL을 직접 작성할 필요 없이 JPA를 통해 객체를 직접 CRUD 할 수 있음
    • JPA가 적절한 SQL을 생성해줌
  • 패러다임의 불일치 해결

JPA는 자바 ORM 기술에 대한 API 표준 명세

  • 쉽게 말하면 인터페이스의 모음
  • JPA를 사용하려면 JPA의 구현체 프레임워크(ex 하이버네이트)를 사용해야 함

JPA를 사용해야 하는 이유

  • 생산성
    • 자바 컬렉션에 객체를 저장하듯이 사용 가능
    • SQL문을 작성하고 JDBC API를 사용하는 지루하고 반복적인 일을 JPA가 대신함
    • 특히 데이터 수정 시 setter 하나만으로 처리 가능
  • 유지보수
    • 기존에는 필드 변경시 관련된 모든 SQL을 수정해야 했음
    • JPA 사용 시 필드만 추가하면 됨
  • 패러다임의 불일치 해결
    • 앞서 살펴본 패러다임의 불일치 문제를 해결해줌
    • 자바 컬렉션을 사용하듯이 데이터베이스를 사용할 수 있음
  • 성능
    • 애플리케이션과 데이터베이스 사이에 계층이 하나 더 있으므로 최적화가 가능
    • 캐싱, 지연 로딩 / 즉시 로딩 …

섹션 2.

섹션 3. 영속성 관리

영속성 컨텍스트

엔티티 매니저

  • 엔티티를 저장하고, 수정하고, 삭제하고, 조회하는 등 엔티티와 관련된 모든 일을 처리(엔티티 관리자)
  • 여러 스레드가 동시에 접근하면 동시성 문제 발생
  • 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않고 트랜잭션을 시작할 때 커넥션을 획득

엔티티 매니저 팩토리

  • 엔티티 매니저를 만드는 공장
  • 만드는 비용이 크므로 보통 한 개만 만들어서 애플리케이션 전체에서 공유
    • 엔티티 매니저를 만드는 비용은 거의 들지 않음
  • 여러 스레드가 동시에 접근해도 안전

영속성 컨텍스트(persistence context)

  • 엔티티를 영구 저장하는 환경
  • 영속성이란?
영속성(persistence)은 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지 않는 데이터의 특성을 의미한다.
  • 엔티티 매니저를 통해 영속성 컨텍스트에 접근하고 영속성 컨텍스트를 관리 가능

엔티티의 생명 주기

  • 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
    • 엔티티 객체를 생성한 뒤 저장하지 않은 순수한 객체 상태
  • 영속(managed): 영속성 컨텍스트에 저장된 상태
    • 엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장 → 영속성 컨텍트스가 관리하는 상태
    • 조회 메서드를 사용해 조회한 엔티티도 영속 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
    • 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않는 상태
    • em.detach를 호출하면 준영속 상태
    • em.close를 호출해서 영속성 컨텍스트를 닫거나 em.clear를 호출해서 영속성 컨텍스트를 초기화해도 해당 영속성 컨텍스트가 관리하던 엔티티는 준영속 상태가 됨.
  • 삭제(removed): 삭제된 상태
    • em.remove를 통해 삭제

영속성 컨텍스트의 특징

  • 영속성 컨텍스트는 엔티티를 식별자 값으로 구분 → 식별자 값 반드시 필요
  • JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 저장된 엔티티를 데이터베이스에 반영 → 플러시(flush)
  • 영속성 컨텍스트의 장점
    • 1차 캐시
    • 동일성 보장
    • 트랜잭션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연 로딩

엔티티 조회

영속성 조회

  • 엔티티 내부에는 영속 상태의 엔티티가 저장되는 1차 캐시가 존재
    • 쉽게 말해 @Id로 매핑한 식별자를 key로, 엔티티 인스턴스를 value로 가지는 Map이 존재
  • em.find 메서드로 조회
    • 첫 번째 파라미터는 엔티티 클래스의 타입
    • 두 번째 파라미터는 조회할 엔티티의 식별자 값
  • 우선 1차 캐시에서 엔티티를 찾고 없으면 데이터베이스에서 조회
  • 데이터베이스에서 엔티티를 조회할 때 1차 캐시에 저장한 후 영속 상태의 엔티티 반환
    • 이후에 해당 엔티티를 조회하면 1차 캐시에서 조회 가능
  • 영속 엔티티의 동일성 보장
    • em.find는 반복 호출해도 1차 캐시에 있는 같은 엔티티 인스턴스 반환
    • 따라서 두 인스턴스의 동일성 비교(==)는 true (엔티티의 동일성 보장)

엔티티 등록

  • em.persist로 등록
  • 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장 x
    • 내부 쿼리 저장소에 INSERT SQL를 모아둠
  • 엔티티 영속화 시 1차 캐시에 저장하면서 등록 쿼리 생성
  • 트랜잭션을 커밋하면 영속성 컨텍스트를 플러시하고 등록된 데이터를 데이터베이스에 반영
  • 쓰기 지연이 가능
    • 데이터 저장 즉시 쿼리를 날리지 않고 쿼리를 저장한 뒤 트랜잭션의 마지막에 한번에 쿼리를 보냄
    • 성능의 최적화 가능

엔티티 수정

  • em.update 메서드는 존재하지 않음
  • JPA는 엔티티의 변경을 감지
    • 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용
    • 즉, 준영속과 같은 상태의 엔티티는 값을 변경해도 반영 x
  • 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 저장(스냅샷)
    • 플러시 시점에 스냅샷과 엔티티를 비교해 변경된 엔티티를 감지하고 UPDATE 쿼리 발생
  • JPA의 기본 전략은 엔티티의 모든 필드를 업데이트
    • 데이터 전송량이 증가하지만 수정 쿼리가 항상 같아서 캐싱 가능
    • 하이버네이트 확장 기능을 활용해 동적 UPDATE SQL 생성 전략 사용 가능

엔티티 삭제

  • em.remove로 삭제
  • 삭제하기 전에 먼저 em.find로 삭제하려는 엔티티를 조회해야 함
  • 엔티티 삭제 역시 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록
    • 영속성 컨텍스트에서는 바로 제거 됨
    • 재사용하지 말고 가비지 컬렉션의 대상이 되도록 두는 것이 좋음
  • 플러시 되는 시점에 삭제 쿼리 전송

플러시

  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
    1. 영속성 컨텍스트의 모든 엔티티를 스냅샷과 비교해서 변경 감지
    2. 수정된 엔티티의 수정 쿼리를 생성해 쓰기 지연 SQL 저장소에 등록
    3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(CRUD 모두)
  • 영속성 컨텍스트를 플러시하는 방법
    • em.flush 직접 호출
      • 거의 사용하지 않음
    • 트랜잭션 커밋 시 자동 호출
    • JPQL 쿼리 실행 시 자동 호출
      • JPQL은 SQL로 변환되기 때문에, JPQL 실행 이전에 영속성 컨텍스트의 내용을 반영해야 함
      • 단, find메서드는 플러시 실행하지 않음
  • 플러시 모드를 직접 지정 가능
    • FlushModeType.AUTO: 커밋이나 쿼리를 실행 시 플러시(기본값)
    • FlushModeType.COMMIT: 커밋할 때만 플러시
  • 주의) 이름은 플러시지만 영속성 컨텍스트에 보관된 엔티티를 지우는 의미가 아님! 동기화의 의미

준영속

준영속 상태

  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 것이므로 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능 사용 불가

em.detach

  • 특정 엔티티를 준영속 상태로 만듦
  • 메서드가 호출되면 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티를 관리하기 위한 모든 정보가 제거

em.clear

  • 영속성 컨텍스트를 초기화
  • 해당 영속성 컨텍스트가 관리하는 모든 엔티티를 준영속 상태로 만듦

em.close

  • 영속성 컨텍스트를 종료
  • 해당 영속성 컨텍스트가 관리하는 모든 엔티티를 준영속 상태로 만듦

준영속 상태의 특징

  • 거의 비영속 상태에 가까움
    • 영속성 컨텍스트가 제공하는 어떤 기능도 동작하지 않음
  • 식별자 값을 가지고 있음
    • 준영속이 되기 위해서는 한 번 영속상태였어야 하므로 식별자를 가짐
  • 지연 로딩 불가능
    • 지연 로딩: 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법
    • 준영속 상태는 영속성 컨텍스트가 관리 x → 지연 로딩 시 문제가 발생

em.merge

  • 준영속 상태의 엔티티를 다시 영속 상태로 변경하기 위해 사용
  • 준영속 상태의 엔티티를 받아 새로운 영속 상태의 엔티티를 반환
    • 기존의 준영속 엔티티를 참조하던 변수는 사용할 필요가 없음
    • 준영속 엔티티를 참조하던 변수를 영속 엔티티를 참조하도록 변경하는 것이 안전
  • 비영속 엔티티도 영속 상태로 만들 수 있음
    • 식별자로 엔티티 조회가 가능하면 불러서 병합
    • 조회가 불가능하면 새로 생성해서 병합
    • 즉, save or update 기능을 수행
profile
Backend Developeer

0개의 댓글