JPA - 영속성

영속성


JPA에서 가장 중요한 2가지가 있다.

  • 객체와 관계형 데이터 베이스 매핑하기 (정적)

  • 영속성 컨텍스트 (내부 동작 방법)

영속성 컨텍스트


JPA가 간단한 코드를 통해 동적으로 쿼리를 생성하여 CRUD를 하는데에는 영속성 이라는 논리적 개념이 숨겨져 있다.

  • JPA를 이해하는데 가장 중요한 용어

  • “엔티티를 영구 저장하는 환경”이라는 뜻

  • 영속성 컨텍스트는 논리적인 개념

  • EntityManager 안에 영속성 컨텍스트라는 논리적 개념 존재

엔티티의 생명주기


  • 비영속 (new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

  • 영속 (managed) : 영속성 컨텍스트에 관리되는 상태

  • 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태

  • 삭제 (removed) : 삭제된 상태

비영속

말그대로 영속상태가 아닌 상태를 뜻한다.

// 객체를 생성한 상태 (비영속)
User user = new User();
user.setId(1L);
user.setName("JPAname");

영속

영속에는 방법이 2가지가 있다.

  • EntityManager.persist(entity);

  • EntityManager.find([Entity.class] , pk);

// 객체를 생성한 상태 (비영속)
User user = new User();
user.setId(1L);
user.setName("JPAname");

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

// 객체를 저장한 상태 (1. 영속)
em.persist(user);

// 객체를 find (2. 영속)
User user = em.find(User.class , 2L);

준영속

준영속은 영속된 상태에서 영속이 끊어졌을 때를 말한다.

방법으로는 크게 3가지가 존재한다.

  • EntityManager.detach(entity) : 특정 엔티티만 준영속 상태로 전환

  • EntityManager.clear() : 영속성 컨텍스트를 완전히 초기화

  • EntityManager.close() : 영속성 컨텍스트를 종료

삭제

삭제 로직이다.

User user = em.find(User.class , 2L);

em.remove(user);

영속성 컨텍스트의 이점


  • 1차 캐시

  • 동일성 (identity) 보장

  • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)

  • 변경 감지 (Dirty Checking)

  • 지연 로딩(Lazy Loading)

1차 캐시

영속성 컨텍스트는 캐시 개념을 가진다.

  • 영속성 등록시 1차캐시 안에 존재하며 find로 1차 캐시 @id값을 찾을 시 DB가 아닌
    1차캐시 먼저 조회수 값을 가져오고 없으면 DB에서 값을 조회후 1차 캐시 등록 후 데이터를 리턴한다.

  • 트랜잭션 종료 후 영속성을 종료하기에 캐시가 다 날라간다.

  • 성능을 올리는 장점보다는 매커니즘을 통한 이점을 가지게 된다.

동일성

1차 캐시의 매커니즘을 통해 동일성을 가지게 된다.


// 데이터를 캐시에 존재하지 않는 다면 DB에서 조회 후 가져온다.
User user = EntityManager.find(User.class , 1L);

// 1L을 가지는 user 데이터가 캐시에 존재하기에 캐시에서 값을 가져온다.
User user2 = EntityManager.find(User.class , 1L);

// == 비교가 가능하다.
if(user == user2){
	// 둘은 같은 캐시에서 값을 참조하기에 true가 나온다.
}

쓰기 지연 SQL

User user = new User();

user.setId(2L);
user.setName("HelloB");

System.out.println("/// START ///");

em.persist(user); // 영속성 추가

System.out.println("/// END ///");

em.getTransaction().commit();

만약 위와 같은 코드를 실행하게 된다면 콘솔에 아래와 같은 출력을 볼 수 있다.

/// START ///
/// END ///
insert into user (id , name) values (1L , 'HelloB') 

분명 START와 END사이에서 실행시켰지만 실제 쿼리는 그 아래인 커밋에서 동작하였다.

그 이유는 쓰기 지연이라는 매커니즘으로 동작하기 때문이다.

  • 1차 캐시 등록시 쓰기지연 SQL 저장소에 인설트 쿼리를 생성한다.

  • JPA는 em.persist(user)에서 인설트 쿼리를 보내는게 아닌 commit() 에서 쿼리를 보낸다.

  • 쓰기 지연은 버퍼링을 가지며 최적화를 할 수 있다.

변경 감지 (Dirty Checking)

JPA는 업데이트시 update()를 작성하는게 아닌 객체를 수정하든 find한 Entity를 set메서드를 통해 값을 수정한다.

수정될때 1차 캐시에서 스냅샷(최초의 데이터)와 현제 데이터를 비교하여 데이터가 맞지 않을 경우
업데이트 쿼리를 쓰기지연 SQL 저장소에 저장하게 된다.

Categories:

Updated:

Leave a comment