[JPA] 영속성 관리

엔티티 매니저는 CRUD작업을 수행하는 관리자 역할을 수행하고, 엔티티를 저장하는 일련의 가상 데이터베이스라고 한다.

엔티티란

엔티티는 관계형 데이터베이스에 매핑한 객체를 말한다.

엔티티 생명주기

  1. 비영속 : 엔티티가 영속성 컨텍스트와 연관이 없는 상태를 의미합니다. Member member = new Member 객체만 생성된 상태
  2. 영속 : 엔티티가 영속성 컨텍스트에 저장된 상태입니다. `em.persist(member)
  3. 준영속 : 엔티티가 영속성 컨텍스트에 있다가 분리된 상태입니다. em.detach(member), em.clear(), em.close()
  4. 삭제 : 엔티티가 영속성 컨텍스트와 데이터베이스에서 삭제된 상태입니다. em.remove(member)

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

엔티티 팩토리란

개발자가 작성한 jdbc, DB의 설정정보를 바탕으로 엔티티 매니저를 생성하는 공장이고, 생성시 커넥션 풀이 만들어지기에 생성하는데 비용이 많이 발생합니다. 그래서 하나의 데이터베이스당 하나의 엔티티 팩토리를 생성하고 전역으로 공유해서 사용합니다.

엔티티 매니저란

엔티티에 대한 CRUD처리를 해주는 관리자 역할을 수행한다. 엔티티 매니저는 여러스레드가 동시에 접근하면 동시성 문제가 발생하기에 스레드간 절대 공유하면 안된다.

영속성 컨텍스트

영속성 컨텍스트는 엔티티를 영구적으로 저장하는 환경으로 엔티티 매니저가 데이터를 조회하거나 저장할때 영속성 컨텍스트에 엔티티를 보관하고 관리합니다.
영속성 컨텍스트는 엔티티 매니저가 생성되면 같이 생성되기에 엔티티 매니저에서 접근이 가능한 것이다.

영속성 컨테스트의 특징

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

1차 캐시에 엔티티 저장

영속성 컨텍스트 내부에 캐시를 가지고 있고 이것을 1차 캐시라고 합니다. 영속상태의 엔티티는 모두 1차 캐시에 저장됩니다.
1차 캐시는 Map구조로 되어있고 엔티티의 식별값인 ID를 key로 엔티티를 value로 데이터를 저장합니다.

엔티티를 저장하면 1차 캐시에 엔티티가 key, value 형식으로 저장됩니다. 엔티티를 DB에서 조회할때 먼저 1차 캐시에 해당 엔티티가 존재하는지 조회하고 있으면 1차 캐시에서 값을 반환하고 없으면 DB에서 조회후에 1차 캐시에 저장후 해당 값을 반환합니다.
따라서 이미 영속성 컨텍스트에 저장된 엔티티를 조회하는 것은 메모리에서 불러오기에 성능상의 이점이 존재한다.

동일성 보장

엔티티를 DB에서 조회할 때 1차캐시를 조회하고 없으면 DB에서 조회후 엔티티를 1차 캐시에 저장후에 반환한다.

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

RDB는 ID가 동일하면 동일한 값이지만 객체는 참조하는 주소가 동일해야 동일성이 만족합니다. 그래서 위와 같이 동일한 엔티티를 조회할때 영속성 컨텍스트에 저장된 동일한 엔티티의 메모리 주소를 참조하는 것이기에 동일성을 보장해줍니다.

트랜잭션을 지원하는 쓰기 지연

엔티티 생성 또는 수정시 엔티티가 영속성 컨텍스트의 1차 캐시에 저장되고 동시에 쓰기지연 SQL 저장소에 SQL이 생성되어 저장됩니다. 그 후에 트랜잭션 커밋을 하면 flush()가 동작하면서 영속성 컨텍스트와 DB를 동기화 시키기 위해서 SQL을 DB에 전달하고 실행시킨 뒤 DB 트랜잭션 커밋을 수행합니다.

변경감지 (dirty checking)

기존에 하나의 테이블에 업데이트가 발생하면 발생하는 상황에 맞게 여러 update SQL을 작성해야 했습니다. 이럴 경우 결국 비즈니스 로직을 파악하기 위해선 SQL을 계속 확인해야하는 문제가 발생합니다. 이런 문제를 해결하기 위해 변경 감지를 지원합니다.

엔티티를 영속성 컨텍스트에 저장할 때 1차캐시에 저장하는데 이때 초기의 값을 스냅샷이라는 공간에 추가적으로 저장하게 됩니다. 그리고 flush()가 동작하면 1차 캐시에 저장되어 있는 엔티티와 스냅샷을 비교해 변경이 되었으면 update SQL을 쓰기 지연 SQL 저장소에 저장합니다. 그후에 해당 SQL을 DB에 전송해 동기화 과정을 거칩니다.
JPA의 수정 전략은 모든 컬럼에 대해 수정 쿼리가 전달됩니다. 즉 모든 필드를 업데이트 시킵니다. 물론 동적인 update 쿼리를 사용하고 싶으면 @DynamicUpdate를 통해 사용이 가능합니다.

플러시

플러시는 영속성 컨텍스트에 있는 데이터를 DB와 연동하는 역할을 합니다. 주로 트랜잭션 커밋을 선언하면 그전에 디폴트로 발동하고 직접 호출하고 싶으면 flush()를 호출하면 되지만 잘 사용하지는 않습니다.