JPA

JPA 와 영속성 관리

엔꾸꾸 2020. 7. 30. 14:35

서론

 

JPA 가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계 와 매핑한 엔티티를 실제로 사용하는 부분으로 나눌 수 있다.

이러한 엔티티들은 엔티티 매니저를 통해 관리 (사용) 할 수 있다.

 

 

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

 

엔티티 매니저 팩토리 는 말 그대로 엔티티 매니저를 만드는 공장 이다.

엔티티 매니저 팩토리는 생성 비용이 매우 크기 때문에, 애플리케이션 당 하나만 만들어 애플리케이션 전체에서 공유 해야 한다.

팩토리가 생성 될 때, 커넥션 풀도 생성된다.

 

엔티티 매니저 는 엔티티를 저장, 수정, 삭제 조회 등 엔티티와 관련된 모든 일을 수행한다.

이름 그대로 엔티티를 관리하는 관리자 역할이다.

엔티티 매니저는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않으며, 일반적으로 트랜잭션이 시작될 때 커넥션을 획득 한다.

엔티티 매니저 팩토리는 스레드 세이프 하지만, 엔티티 매니저는 그렇지 않다. 스레드간 공유해서는 안되는 자원이다.

 

 

영속성 컨텍스트

 

영속성 컨텍스트 (Persistence Context) 는 엔티티를 영구 저장하는 환경 이다.

엔티티 매니저를 통해 엔티티를 관리하면 영속성 컨텍스트를 통해 관리하게 된다.

 

 

영속성 컨텍스트를 통해 관리되는 엔티티는 생명주기 (Life Cycle) 가 존재하는데, 아래의 4가지로 구분된다.

  1. 비영속 (new/transient) 상태
  2. 영속 (managed) 상태
  3. 준영속 (detached) 상태
  4. 삭제 (removed) 상태

비영속 상태는 transient 상태라고 하며 영속성 컨텍스트와는 전혀 관련이 없는 상태를 말한다.

일반적으로 new 키워드를 통해 새롭게 생성한 경우 여기에 해당한다.

Member member = new Member("member1", "회원1");

 

영속 상태는 managed 상태라고 하며 영속성 컨텍스트가 관리하는 상태를 말한다.

엔티티가 영속화 되었을 경우 여기에 해당한다.

Member member = new Member("member1", "회원1");
em.persist(member); // 영속화

 

준영속 상태는 detached 상태라고 하며 영속성 컨텍스트가 관리중이 었다가 관리를 하지 않는 상태를 말한다.

엔티티가 영속화 되었다가, 영속 상태에서 벗어난 경우 여기에 해당한다.

일반적으로 개발자가 직접 엔티티를 준영속화 시키는 경우는 드물다.

Member member = new Member("member1", "회원1");
em.persist(member); // 영속화
em.detach(member); // 준영속화

 

영속 상태의 엔티티를 준 영속 상태로 만드는 방법

  1. em.defach(entity);
    • 특정 엔티티를 준영속화 한다.
  2. em.clear();
    • 영속성 컨텍스트를 완전히 초기화 한다
  3. em.close();
    • 영속성 컨텍스트를 종료한다.

 

준영속 상태의 특징

 

  1. 비영속 상태에 가깝다.
    • 영속성 컨텍스트가 관리하지 않으므로, 1차캐시 , 쓰기 지연 등 영속성 컨텍스트가 제공하는 기능이 동작하지 않는다.
  2. 식별자 값을 가지고 있다.
    • 이미 한번 영속화 되었기 때문에 반드시 식별자를 가지고 있다.
  3. 지연 로딩을 할 수 없다.
    • 영속성 컨텍스트가 관리하지 않으므로 지연 로딩시 문제가 발생한다.

Merge가 머지 ?

 

준영속 상태의 엔티티를 다시 영속 상태로 만들려면 Merge 를 사용해야 한다.

merge() 메소드는 준영속 상태의 엔티티를 받아, 해당 정보를 기반으로 새로운 영속 상태의 엔티티를 만들어 반환한다.

병합은 준영속, 비영속 상태를 신경 쓰지 않는다. 식별자로 엔티티 조회가 가능하다면 병합을 하고, 없다면 새롭게 생성하여 병합을 시도한다. (save or update)

주의 할 점은 merge() 를 통해 생성된 엔티티는 파라메터와 반환된 객체가 동일하지 않다.

 

 

삭제 상태는 removed 상태라고 하며 엔티티가 영속성 컨텍스트와 데이터베이스에서 삭제된 경우를 말한다.

Member member = new Member("member1", "회원1");
em.persist(member); // 영속화
em.detach(member); // 준영속화
em.remove(member); // 삭제

 

 

영속성 컨텍스트의 특징

 

영속성 컨텍스트는 엔티티를 구분할때 엔티티의 식별자 값으로 구분하기 때문에 영속 상태의 엔티티는 반드시 식별자 값이 존재해야 한다.

JPA는 일반적으로 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 존재하는 엔티티를 데이터베이스에 반영하는데 이것을 플러시 (flush) 라고 한다.

 

영속성 컨텍스트를 사용할때의 장점은 5가지 정도가 있다고 한다.

  1. 1차캐시
  2. 엔티티간 동일성 보장
  3. 트랜잭션을 지원하는 쓰기 지연
  4. 변경 감지
  5. 지연 로딩

 

 

1차 캐시

 

영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시 라고 한다.

영속 성태의 엔티티는 모두 이곳에 저장된다.

Member member = new Member("member1", "회원1");
em.persist(member) // 영속화

 

1차 캐시에서 조회

 

영속성 컨텍스트에서 엔티티를 조회할 경우 먼저 1차 캐시에서 식별자 값을 이용해 엔티티 조회를 시도한다.

영속성 컨텍스트에 엔티티가 존재하지 않는다면, 데이터베이스에서 새롭게 조회한 뒤 영속성 컨텍스트에 저장을 하고

해당 엔티티를 반환한다.

Member member = new Member("member1", "회원1");
em.persist(member) // 영속화
  
Member findMember = em.find(Member.class, "member1"); // 1차캐시에서 조회

 

엔티티의 동일성 보장

 

같은 트랜잭션 내에서 영속성 컨텍스트는 동일 식별자에 대한 엔티티에 대해 동일성을 보장한다.

같은 트랜잭션 내에서는 수차례 조회 하더라도 1차 캐시에 존재하는 같은 인스턴스를 반환한다.

Member member = new Member("member1", "회원1");
em.persist(member) // 영속화
  
Member findMember1 = em.find(Member.class, "member1"); // 1차캐시에서 조회
Member findMember2 = em.find(Member.class, "member1"); // 1차캐시에서 조회

findMember1 == findMember2; // true

 

 

쓰기 지연

 

엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 SQL 을 모아뒀다가 트랜잭션을 commit 하는 시점에 모아둔 쿼리를

한번에 데이터베이스로 보내게 되는데 이를 트랜잭션을 지원하는 쓰기지연 (write-behind) 라고 한다.

 

 

변경 감지

 

기존에 SQL 을 사용하는 방식은 수정 쿼리를 직접 작성 해야한다.

엔티티가 수정되거나, 비즈니스가 변경되면, SQL도 동일하게 변경되어야 하는데 SQL을 수정하는 과정에서 오탈자 (휴먼에러) 가 발생할 수 있고, 여러 개의 수정쿼리가 발생할 수 있다.

JPA로 엔티티를 수정할 때는 엔티티를 조회해서 데이터만 변경하면 된다.

트랜잭션 커밋 직전에 스냅샷과 엔티티의 변경점을 비교해 데이터베이스에 자동으로 반영을 해주는 기능을 지원하는데 이런 기능을 변경 감지 (dirty-checking) 이라고 한다.

Member findMember = em.find(Member.class, "member1");
findMember.setName("changeMemberName");
변경 감지 기능은 영속성 컨텍스트가 관리하는 영속상태의 엔티티에만 적용된다.
영속성 컨텍스트가 관리하지 않는 엔티티는 값이 변경되어도 데이터베이스에 관리되지 않는다.

 

JPA의 데이터 수정 기본 전략 은 엔티티의 모든 필드를 수정한다.

데이터 전송량이 증가하지만, 쿼리를 재사용할 수 있기 때문에 성능상 이점을 가져오게 된다.

또한 일반적으로 컬럼이 30개가 넘어가지 않는 이상, 모든 컬럼을 수정하는 쿼리를 재사용하는 것이 성능한 이득이다.

 

플러시

 

플러시 (flush) 는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는것을 말한다.

일반적으로 flush 하는 방법은 아래 3가지가 있다.

 

  1. em.flush()
  2. 트랜잭션 커밋시 자동 호출
    • 변경 내용을 SQL 로 전달하지 않고 트랜잭션만 커밋하면 어떤 데이터도 데이터베이스에 반영되지 않는다.
  3. JPQL 쿼리 실행시 자동 호출
    • JPQL 실행 이전에 엔티티 매니저를 통해 엔티티를 영속화 했다면, 데이터베이스에는 해당 엔티티에 대한 정보가 없다.
    • JPQL 은 SQL로 변환되어 데이터베이스 에서 엔티티를 조회하기 때문에 영속화된 엔티티를 조회할 수 없다.

 

정리

  • 엔티티 매니저는 엔티티 매니저 팩토리를 통해 생성할 수 있다.
  • 엔티티 매니저 팩토리는 애플리 케이션 당 하나만 생성을 해야하며 엔티티 매니저는 스레드간 공유해서는 안된다.
  • 엔티티 매니저를 사용하면 영속성 컨텍스트를 통해 엔티티를 관리하게 되는데 1차캐시, 동일성 보장, 쓰기지연, 변경감지, 지연 로딩 등을  제공한다.
  • 영속성 컨텍스트에 저장한 엔티티는 flush 되어야만 데이터베이스에 반영 된다.