[JPA] 영속성 관리

왈왈왈 (Yoon tae uk)·2022년 4월 24일
1

영속성 관리

JPA에서의 영속성 관리에 대해 알아봅니다.


Goal

  • 엔티티 매니저와 엔티티 매니저 팩토리에 대해 알아봅니다.
  • 영속성 컨텍스트에 대해 알아봅니다.

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

EntityMangerFactory

데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 EntityMangerFactory를 하나만 생성합니다.

EntityMangerFactory 생성시

// 공장의 개념으로 생성비용이 많이든다.
EntityManagerFatory emf = Persistence.createEntityManagerFactory("db");

EntityMangerFactory는 EntityManager를 생산해주는 클래스입니다.

  • EntityMangerFactory를 “공장"의 개념 EntityManager를 “제품"의 개념으로 생각합니다.
  • EntityMangerFactory의 역할은 EntityManager 만을 생성하기 때문에 여러 스레드가 접근하여 자원을 공유하여도 안전합니다.

EntityManager

엔티티 매니저는 엔티티를 저장, 수정, 삭제, 조회 등 메모리상의 가상 데이터베이스입니다. 엔티티와 관련된 모든 일을 합니다. 보통 트랜잭션에 맞춘 라이프사이클을 가지고 있습니다.

EntityManager 생성시

EntityManagerFatory emf = Persistence.createEntityManagerFactory("db");
// 생성시 비용이 거의들지않습니다.
EntityManager em = emf.createEntityManager();
  • 위의 설명처럼 EntityManager를 제품의 개념으로 생각합니다.
  • EntityMangerFactory와 달리 Thread Safe 하지 않기 때문에 스레드의 자원들을 공유하면 동시성 문제가 발생합니다.

영속성 컨텍스트

  • “엔티티를 영구 저장하는 환경"을 뜻합니다.
  • 영속성 컨텍스트는 논리적이거나 추상적인 개념으로 바라봅니다.
  • 여러 엔티티매니저가 하나의 영속성 컨텍스트를 공유할 수 있습니다.
em.persist(item); // item을 영속화

위의 설명에서는 엔티티를 영구 저장하는 환경이라고 말했지만, 프로그램이 종료 전에 엔티티 매니저를 초기화 시키거나 종료시키는 코드를 만나면 영속성 컨텍스트는 소멸합니다.

이후에, commit이 일어나면 영속성 컨텍스트에서 쿼리를 만들어서 실제 데이터베이스에 반영합니다.


엔티티의 생명주기

  • 비영속 : 영속성 컨텍스트와 전혀 상관이 없는 상태입니다.
  • 영속 : 영속성 컨텍스트에 관리되는 상태입니다.
  • 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태입니다.
  • 삭제 : 삭제된 상태입니다.

비영속
엔티티 객체가 존재하지만 영속성 컨텍스트에는 저장하지 않은 상태입니다.

Member member = new Member();
//em.persist(member) // 영속화 x

영속
엔티티 객체를 영속성 컨텍스트에 저장한 상태입니다.

Member member = new Member();
em.persist(member);

준영속
영속상태의 엔티티 객체를 영속성 컨텍스트에서 분리된 상태입니다.

Member member = new Member();
// 영속성 컨텍스트에의해 관리
em.persist(member);
// 준영속 상태 (분리)
em.detach(member);

삭제
삭제된 상태입니다.

Member member = new Member();
em.remove(member);

영속성 컨텍스트의 특징

영속성 컨텍스트의 식별자 값

  • 엔티티를 식별자 값으로 구분합니다.
  • 영속 상태에는 식별자 값이 꼭 필요합니다.
  • 식별자가 없을 경우에는 예외가 발생합니다.

영속성 컨텍스트 데이터베이스 저장

  • 트랜잭션을 커밋(commit)하는 순간 데이터베이스에 영속성 컨텍스트에 저장된 엔티티를 반영합니다.
  • 영속성 컨텍스트를 반영하는 것을 플러시(flush)라고 합니다.

플러시와 커밋의 차이는?

flush는 트랜잭션과 함께 엔티티를 즉시 데이터베이스에 반영하지만, 변경 사항을 커밋하지 않을 수 있습니다.

하지만 commit을 하는 순간 영속성 컨텍스트의 엔티티를 반영하며 롤백할 수 없습니다.

영속성 컨텍스트를 사용하면 장점

  • 1차 캐시
  • 트랜잭션을 이용하는 쓰기 지연
  • 더티 체킹
  • 지연 로딩
  • 동일성 보장

엔티티 조회

Member member = new Member();
member.setId("1);
member.setName("dog");

em.persist(member);

영속성 컨텍스트는 내부에 캐시를 가지고 있습니다, 이를 1차 캐시라고 부릅니다.

영속 상태의 엔티티를 저장하면 이곳에 저장합니다.

1차 캐시의 내부는 Map의 형태로 이루어져 있습니다. 키는 @id로 매핑만 식별자로 사용하고 인스턴스의 값은 엔티티를 사용합니다.


1차 캐시에서 조회

Member member = new Member();
member.setId("1);
member.setName("dog");

em.persist(member);
Member member = em.find(Member.class, "dog");

em.find()를 호출하는 순간 먼저 1차 캐시에서 엔티티를 찾은 뒤 데이터가 존재하지 않으면 데이터베이스에서 조회합니다.

데이터베이스에서 조회

Member member = new Member();
member.setId("1);
member.setName("dog");

em.persist(member);
Member member = em.find(Member.class, "cat"); // 강아지말고 고양이
  1. em.find()를 호출하는 순간 1차 캐시를 조회합니다.
  2. 1차 캐시에 찾고 있는 엔티티가 존재하지 않으면 데이터베이스를 조회합니다.
  3. 데이터베이스에서 조회한 데이터를 1차 캐시에 저장합니다. (영속 상태)
  4. 조회한 엔티티를 반환합니다.

1차 캐시가 만들어진 이유는?

데이터베이스를 커넥션 하는 과정은 서버에 부하가 많이 듭니다.

엔티티를 다시 사용할 일이 있으면 데이터베이스에 접근하지 않고 빠르게 1차 캐시에 있는 엔티티를 사용하여 성능상 이점을 얻을 수 있습니다.

하지만, 한편으로는 엔티티 매니저는 트랜잭션을 기준으로 생성과 삭제를 하기 때문에 데이터베이스도 트랜잭션을 단위로 캐싱 작업을 진행하기 때문에 성능상 큰 이점을 가질 수 없습니다.


엔티티 동일성 보장

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

System.out.println(member1 == member 2);
  • member1과 member2는 true가 나옵니다.
  • 영속성 컨텍스트는 만약 데이터가 1차캐시에 존재하면 1차캐시에서 엔티티를 조회하여 반환하므로 엔티티의 동일성을 보장합니다.

엔티티 등록

EntityManger em = emf.createEntityManager();

EntityTransaction tr = em.getTransaction();

em.persist(member1);
em.persist(member2); // 현재까지 SQL문을 DB에 보내지않습니다.

tr.commit(); //커밋하는 순간 SQL문을 DB에 INSERT를 합니다.
  1. 엔티티 매니저는 트랜잭션이 커밋하기 전에는 데이터베이스에 엔티티를 저장하지 않습니다.
  2. 내부 저장소에 INSERT SQL을 저장해둡니다.
  3. 트랜잭션 커밋하는 순간 쿼리를 데이터베이스에 보냅니다.

트랜잭션이 지원하는 쓰기 지연을 사용하는 이유
데이터베이스와 커넥션 하는 것은 많은 자원을 소모합니다.
쿼리를 보낼 때 마다 데이터베이스와 잦은 커넥션을 한다면 애플리케이션의 성능을 낮추기 때문에 쿼리를 저장소에 모아놓고 한 번에 전송하는 쓰기 지연 방식을 사용합니다. 자바의 버퍼의 방식과 유사합니다.


엔티티 수정



Member member = em.find(Member.class, "member1"); // 조회

member.setName("spring");
member.setEmail("0000@naver.com"); 
 
// em.update(member); 

transaction.commit();
  • JPA에서는 단순히 엔티티를 조회한 뒤 값만 변경해줍니다.
  • em.update()라는 메서드는 존재하지 않습니다.
  • 변경 감지(더티 체킹) 기능을 사용하여 데이터베이스에 반영합니다.

왜 update가 필요없는가?

  • 사상 때문이다. 자바에서 컬렉션에서 리스트의 값을 수정할 때 값을 수정후 다시 저장하지 않습니다.
  • 컬렉션의 리스트랑 비슷한 컨셉입니다.

변경 감지

  • 트랜잭션을 커밋하는 순간 내부에서 플러시를 호출합니다.
  • 엔티티와 스냅샷을 비교하여 변경된 내용을 감지합니다.
  • 변경된 엔티티가 있으면 JPA가 수정 쿼리를 생성하여 쓰기 지연 SQL 저장소에 쿼리를 보냅니다.
  • SQL 저장소를 데이터베이스에 보냅니다.

단, 변경감지는 엔티티 컨텍스트에 관리되는 영속성의 엔티티에만 적용됩니다.

업데이트 기본 전략

JPA의 업데이트 기본전략은 엔티티의 모든 필드를 업데이트 합니다.

  • 생성되는 쿼리가 같아 실행시점에 미리 만들어서 재사용이 가능합니다.
  • 동일한 쿼리를 받으면 이전에 파싱된 쿼리를 사용할 수 있습니다.

엔티티 삭제

Member member1 = em.find(Member.class, "member1);
em.remove(member1); // 삭제

트랜잭션이 커밋이 될 때 DELETE 쿼리가 나갑니다.


플러시

  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영합니다.
  • 트랜잭션 커밋시에 내부적으로 플러시가 동작하는데, 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다.

영속성 컨텍스트를 플러시하는 방법

  1. em.flush() 직접 호출
    • 테스트를 하거나 다른 프레임워크와 JPA를 함께 사용할 때 사용합니다.
  2. 트랜잭션 커밋시 플러시 호출
  3. JPQL사용시 호출
    • member는 영속화가 되어있고 쿼리는 날라가지 않은 상태입니다.
    • JPQL에서 SELECT문을 사용하여 member를 조회하려고 하면 member는 데이터베이스에 쿼리가 아직 반영되지 않은 상태입니다.
    • JPA에서는 이런 상황을 방지하기 위하여 JPQL을 사용하기 직전에 flush()를 호출하여 데이터베이스와의 싱크를 맞춥니다.

JPQL 사용시

em.persist(member1);
em.persist(member2);

// JPQL실행 (flush를 호출한다.)
query = em.createQuery("select m from Member m", Member.class); 

플러시 옵션 모드

  • FlushModeType.AUTO
    • 커밋이나 쿼리를 실행할 때 플러시 (기본값)
  • FlushModeType.COMMIT
    • 커밋할 때만 플러시

플러시 정리

플러시는 영속성 컨텍스트의 내용을 비우지 않습니다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스와 동기화하는 것입니다.

플러시를 사용할 수 있는 이유는 트랜잭션이라는 작업 단위 때문입니다. 트랜잭션 커밋 직전에만 변경 내용을 데이터베이스와 동기화를 시키면됩니다.


준영속 상태

영속성 컨텍스트가 관리하는 엔티티가 영속성 컨텍스트에서 분리된 상태입니다.

Member member = em.find(Member.class, "member1");
member.setName("spring");

em.detach(member); // 준영속

transaction.commit();
  • em.detach()를 호출하여 member를 준영속상태로 만듭니다.
  • 트랜잭션을 커밋한 뒤 아무 쿼리도 전송하지 않습니다.
  • JPA에서 더이상 관리하지 않는 객체가 됩니다.

clear()
영속성 컨텍스트 자체를 초기화 시킵니다.
영속성 컨텍스트에 존재하는 모든 엔티티를 준영속 상태로 만듭니다.

close()
영속성 컨텍스트를 종료시킵니다.
영속성 컨텍스트에 존재하는 모든 엔티티를 준영속 상태로 만듭니다.


Reference

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

0개의 댓글

관련 채용 정보