[JPA] 자바 ORM 표준 프로그래밍 정리 #3

y__hi_2X·2022년 2월 10일
1

JPA

목록 보기
3/4

이 글은 김영한님의 '자바 ORM 표준 프로그래밍' 교재를 바탕으로 정리한 글입니다. :)

이번 글에서는 JPA의 강점 중 하나인 영속성 관리에 대해 설명해보겠습니다~!🕺💃

엔티티 매니저

엔티티 매니저: 엔티티 저장, 수정, 조회 등 엔티티와 관련된 모든 일을 처리하며 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다(ex: 트랜잭션 시작 시). 여러 스레드가 동시에 접근하면 동시성 문제가 발생하기에 스레드 간에 공유해선 안된다.

엔티티 매니저 팩토리: 이름 그대로 엔티티 매니저를 만드는 공장으로, 이 공장을 만드는 비용이 상당히 크다. 때문에 애플리케이션 전체에서 엔티티 매니저 팩토리 하나를 공유해서 사용한다. 스프링 부트에서는 이를 자동으로 설정해준다.

엔티티의 생명주기

  • 비영속(new/transient): 영속성 컨텍스트에 속하지 않은 상태
  • 영속(managed): 영속성 컨텍스트에 저장된 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 삭제된 상태

영속성 컨텍스트

💡 영속성 컨텍스트란?
JPA를 이해하는 데 있어 가장 중요한 개념이다. '엔티티를 영구 저장하는 환경'이라는 뜻으로 엔티티 매니저로 엔티티를 저장하거나 조회할 때 영속성 컨텍스트에 엔티티가 1차적으로 보관 및 관리된다.

특징

  • 식별자 값으로 구분
    영속성 컨텍스트는 엔티티를 식별자 값(@Id, 테이블의 기본 키에 매핑)으로 구분하기에 식별자 값이 반드시 있어야 한다.
  • 트랜잭션 커밋 시 데이터베이스에 저장
    JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 저장된 엔티티를 데이터베이스를 반영한다. 이것을 플러시(flush)라 한다.

장점

  • 1차 캐시
    영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라 한다. 캐시에 저장된 엔티티는 @Id로 매핑된 식별자로 구분된다.
    엔티티 조회 시 가장 먼저 메모리에 있는 1차 캐시를 검색한다. 만약 찾는 엔티티가 캐시에 없으면 엔티티 매니저는 데이터베이스를 조회해 엔티티를 생성한 후 1차 캐시에 저장한 뒤 해당 엔티티를 반환한다.
    아래 예시 코드로 관련 내용을 확인할 수 있다.

   Member member = new Member();
   member.setId("member1");

   //1차 캐시에 저장
   em.persit(member);
   
   //1차 캐시에서 조회
   Member findMember1 = em.find(Member.class, "member1");
   
   //DB에서 조회
   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
  • 트랙잭션을 지원하는 쓰기 지연
    엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 SQL을 모아둔다. 그래고 트랜잭션 커밋 시 모아둔 쿼리를 동시에 보낸다. 이 기능을 통해 모아둔 등록 쿼리를 데이터베이스에 한 번에 전달하면 성능을 최적화할 수 있다.
  • 변경 감지
    JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태인 스냅샷을 복사해서 저장한다. 그리고 플러시 시점에 스냅샷과 엔티티를 비교해 변경이 감지되었을때 수정 쿼리를 생성해 내부 쿼리 저장소에 저장한다.

    또한 JPA 기본 전략으로 인해 변경 감지 시 모든 필드가 업데이트 된다. 이 전략은 수정 쿼리가 항상 같고 데이터베이스에서 한 번 파싱된 쿼리를 재사용할 수 있다는 장점이 있다.

    필드가 많거나 저장되는 내용이 너무 큰 경우에는 하이버네이트 확장 기능(@DynamicUpdate)를 사용하면 되지만, 이를 사용하면 캐시된 쿼리를 사용하지 않고 새로운 쿼리를 생성해 오버헤드가 발생한다고 합니다... 약 30개 정도되는 컬럼을 수정하는 경우는 되어야 기본 전략보다 빠르다고 하네요...

플러시

플러시(flush)는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다. 실행 시 변경 감지가 동작해 수정 엔티티에 대한 쿼리를 생성하고 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.

호출 방법

플러시 호출 방법은 아래 세 가지가 있다.

  • 직접 호출: flush() 함수를 직접 호출해 영속성 컨텍스트를 강제로 플러시한다. 테스트나 특수한 경우를 제외하고 거의 사용하지 않는다.
  • 트랜잭션 커밋 시 자동 호출: JPA는 트랜잭션 커밋 시 플러시를 자동으로 호출해준다.
  • JPQL 실행 시 자동 호출: JPQL과 같은 객체지향 쿼리를 호출할 때도 JPA가 자동으로 플러시를 호출해준다.

옵션

  1. FlushModeType.AUTO(Default): 커밋이나 쿼리 실행 시 플러시
  2. FlushModeType.COMMIT: 커밋할 때만 플러시
em.setFlushMode(FlushModeType.COMMIT);

준영속

준영속이란 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 상태로, 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.

준영속 상태로 전환되는 경우는 아래 세가지이다.

  • 준영속 상태로 전환: em.detach(entity)
    영속성 컨텍스트에 더 이상 해당 엔티티를 관리하지 말라고 알려준다. 호출 시 1차 캐시와 쓰기 지연 SQL 저장소에서 해당 엔티티 관련된 모든 정보가 제거된다.
  • 영속성 컨텍스트 초기화: em.clear()
    영속성 컨텍스트를 초기화해 모든 엔티티를 준영속 상태로 만든다.
  • 영속성 컨텍스트 종료: em.close()
    영속성 컨텍스트 자체를 종료시킨다.

병합

준영속이나 비영속 상태의 엔티티를 영속 상태로 변경하기 위해 병합 함수인 merge()를 사용한다. 병합 함수는 파라미터로 엔티티를 받아 식별자로 영속성 컨텍스트와 데이터베이스를 조회한 후 찾는 엔티티가 없으면 받아온 엔티티 정보로 새로운 영속 상태의 엔티티를 생성해 반환한다.

public static void main(String args[]){
	Member member = createMember("memberA", "userA");
    
    member = mergeMember(member);
}

public Member createMember(String id, String name){
    EntityManager em;

    //회원 생성
    Memeber member = new Member();
    member.setId(id);
    member.setName(name);

    em.persist(member);

    //영속성 컨텍스트 종료
    em.close();
}

public Member mergeMember(Member member){
	EntityManager em;
    
	//병합 엔티티 반환
	return em.merge(member);
}

읽어주셔서 감사합니다. 🐥

0개의 댓글