본문 바로가기
JPA

[JPA] 영속성 컨텍스트란? JPA 에서 정말 중요한 개념인 영속성 컨텍스트에 대해 알아보자 - 개발하는 지토

by 개발하는 지토 2020. 12. 19.

JPA - 자바 퍼시스턴스 API


JPA에서 가장 중요한 2가지!!

  1. 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
  2. 영속성 컨텍스트

오늘의 글은 2번 영속성 컨텍스트를 공부하며 정리 한 내용이다.


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

고객의 요청이 오면 엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다.

생성된 앤티티 매니저는 내부적으로 데이터베이스 커넥션을 사용해서 DB를 사용하게 된다.

엔티티 매니저는 각 고객의 요청마다 생성된다.


영속성 컨텍스트

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

"엔티티를 영구 저장하는 환경" 이라는 뜻

 

entityManage.persist(entity);

 

위 코드는 DB에 entity를 저장한다는 것이 아니라 영속성 컨텍스트를 통해서 entity를 영속한다는 뜻이다.

 

  • entity를 DB에 저장하는 것이 아니라 영속성 컨텍스트에 저장한다는 것이다.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.
  • 일단 기본적인 J2SE 환경에서는 엔티티 매니저와 영속성 컨텍스트가 1:1 관계를 가진다.


 

엔티티의 생명주기

  • 비영속 (new/tansient)

    영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

  • 영속 (menaged)

    영속성 컨텍스트에 관리되는 상태

  • 준영속 (detached)

    영속성 컨텍스트에 저장되었다가 분리된 상태

  • 삭제 (removed)

    삭제된 상태


비영속

// 객체 생성 (비영속)
Entity entity = new Entity();
entity.setId("entity1");
entity.setName("지토");

영속

// 객체 생성 (비영속)
Entity entity = new Entity();
entity.setId("entity1");
entity.setName("지토");

EntityManager em = emf.createEntityManager(); // emf : 엔티티 매니저 팩토리
em.getTransaction().begin();

// 객체를 저장 (영속)
em.persist(entity);

준영속

// 객체 생성 (비영속)
Entity entity = new Entity();
entity.setId("entity1");
entity.setName("지토");

EntityManager em = emf.createEntityManager(); // emf : 엔티티 매니저 팩토리
em.getTransaction().begin();

// 객체를 저장 (영속)
em.persist(entity);

// 엔티티를 영속성 컨텍스트에서 분리 (준영속)
emdetach(entity);

삭제

// 객체 생성 (비영속)
Entity entity = new Entity();
entity.setId("entity1");
entity.setName("지토");

EntityManager em = emf.createEntityManager(); // emf : 엔티티 매니저 팩토리
em.getTransaction().begin();

// 객체를 저장 (영속)
em.persist(entity);

// 객체를 삭제 (삭제)
em.remove(entity);

영속성 컨텍스트의 이점

 

  1. 1차 캐시
  2. 동일성(identity) 보장
  3. 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
  4. 변경 감지 (Dirty Checking)
  5. 지연 로딩 (Lazy Loading)

엔티티 조회, 1차 캐시

// 객체 생성 (비영속)
Entity entity = new Entity();
entity.setId("entity1");
entity.setName("지토");

// 객체를 저장 (영속)
em.persist(entity);

 

persist를 하면 해당 엔티티를 1차 캐시에 저장한다 ( 영속 )


1차 캐시에서 조회

// 객체 생성 (비영속)
Entity entity = new Entity();
entity.setId("entity1");
entity.setName("지토");

// 1차 캐시에 저장됨
em.persist(entity);

// 1차 캐시에서 조회
Entity findEntity = en.find(Entity.class, "entity1");

 

1차 캐시에 조회하려는 id 값의 엔티티가 있을 경우에는 해당 값을 반환한다.


데이터베이스에서 조회

Entity findEntity2 = en.find(Entity.class, "entity2");

 

1차 캐시에 조회하려는 엔티티의 id가 없을 경우 DB에서 조회를 해와서 1차 캐시에 저장을 하고 해당 객체를 반환한다.

 

이후에 entity2를 조회하면 1차 캐시에 값이 있기 때문에 1차 캐시에서 조회한다.

 

하지만 해당 시나리오(1차 캐시)는 트랜잭션 단위로 공유되는 캐시이기 때문에, 그렇게 큰 성능의 이점을 얻는다 라는 장점은 거의 없다.


영속 엔티티의 동일성 보장

Entity findEntity1 = en.find(Entity.class, "entity1);
Entity findEntity2 = en.find(Entity.class, "entity2");

assertThat(findEntity1 == findEntity2).isTrue(); // 테스트 성공

 

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공


엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // 트랜잭션 시작

em.persist(entity1);
em.persist(entity2);

// 커밋하는 순간 데이터베이스에 INSERT QUERY를 보냄
transaction.commit(); // 트랜잭션 커밋

 

기본적으로 JPA에서는 트랜잭션을 시작하고 persist를 실행하면 insert sql을 db에 보내지 않고 JPA가 쌓아두고 있는다.

 

commit 하는 순간에 쌓아둔 Query들을 보낸다.


엔티티 수정 - 변경 감지 (Dirty checking)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // 트랜잭션 시작

// 영속 엔티티 조회
Entity entity = em.find(Entity.class, "entity");

// 영속 엔티티 데이터 수정
entity.setName("지토");

// em.update(entity) <-- 이런 코드가 없는데??

transaction.commit(); // 트랜잭션 커밋
  1. JPA는 commit 시점에 내부적으로 flush가 호출이 된다.
  2. 영속 컨텍스트(entityManager)에서 엔티티와 스냅샷을 비교한다. (여기서 스냅샷은 해당 entity가 처음에 영속상태가 됐을 때 첫 시점의 entity 상태이다.)
  3. 이때 entity가 변경이 된 값이 있다면 쓰기 지연 SQL 저장소에 update쿼리를 만들고 DB에 반영을 하게 된다.

엔티티 삭제

// 삭제 대상 엔티티 조회
Entity entity = em.find(Entity.class, "entity");

em.remove(entity); // 엔티티 삭제

변경 감지 메커니즘과 똑같은 메커니즘으로 해당 엔티티가 삭제가 된다고 보면 된다.

댓글