ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 3. 영속성 관리 - 영속성 컨텍스트
    ComputerScience/JPA 2022. 6. 11. 17:02

    영속성 컨텍스트

    JPA에서 가장 중요한 2가지가 있습니다. 첫번째는 이전 장에서 설명했던 관계형 데이터베이스를 매핑하는 것이고 두번째는 영속성 컨텍스트입니다.

     

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

    처음 생성된 엔티티 매니저 팩토리는 클라이언트의 요청에 따라 엔티티 매니저를 생성합니다. 그리고 엔티티 매니저는 DB Connection Pool에 있는 Connection을 이용해서 DB에 접근합니다.

     

    DB Connection Pool
    DB Connection 생성 자체가 리소스를 많이 소모하기 때문에 미리 Connection들을 생성해둔 뒤 DB 연결에 대한 요청이 올때마다 사용하기 위한 Pool

     

    영속성 컨텍스트란?

    • JPA를 이애하는데 가장 중요한 용어입니다.
    • "엔티티를 영구 저장하는 환경"이란 뜻입니다.
    • EntityManager.persist(entity);
      • persist한다는 것은 DB에 저장한다는 것이 아니라 실제로는 엔티티를 영속성 컨텍스트에 저장한다는 뜻입니다.
    • 영속성 컨텍스트는 논리적인 개념입니다. 눈에 보이지 않습니다.
    • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근합니다.

     

    엔티티의 생명주기

     

    그렇다면 엔티티의 영속성은 어떤 생명주기를 갖고 있을까요? 비영속, 영속, 준영속, 상속과 같은 상태를 갖고 있습니다.

    • 비영속(new/transient)
      영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
    • 영속(managed)
      영속성 컨텍스트에 의해 관리되는 상태
    • 준영속(detached)
      영속성 컨텍스트에 저장되었다가 분리된 상태
    • 삭제(removed)
      삭제
      된 상태

     

     

    비영속

    //객체를 생성한 상태(비영속)
    Member member = new Member(); 
    member.setId("member1"); 
    member.setUsername("회원1");

    영속 컨텍스트에 들어있지 않은 상태입니다. 객체를 단순히 생성한 상태는 비영속 상태입니다.

     

     

     

     

    영속

    //객체를 생성한 상태(비영속) 
    Member member = new Member(); 
    member.setId("member1"); 
    member.setUsername(“회원1”);
    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    //객체를 저장한 상태(영속)
    em.persist(member);

    객체를 생성한 뒤 엔티티 매니저를 통해 persist 해주면 영속 상태가 됩니다.

     

     

    준영속,삭제

    엔티티를 영속성 컨텍스트에서 분리하는 작업을 마치면 준영속 상태가 되고 삭제작업을 진행하면 엔티티가 삭제됩니다.

    //회원 엔티티를 영속성 컨텍스트에서 분리(준영속 상태)
    em.detach(member);
    
    //객체를 삭제한 상태
    em.remove(member);

     

     

    자칫 보면 복잡해 보일 수 있는 영속성 상태를 사용하는 이유는 무엇일까요? 영속성 상태를 이용한다면 다음과 같은 이점들을 얻을 수 있습니다.

     

    영속성 컨텍스트의 이점

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

     

    1. 1차 캐시

    영속된 엔티티들은 영속 컨텍스트의 1차캐시에 저장되기 때문에 이미 1차캐시에 있는 엔티티를 찾기 위해서 쿼리를 실행할 필요가 없게 됩니다.

     

    Member member = new Member();
    member.setId("member1");
    member.setUsername("회원1");
    
    //1차 캐시에 저장됨
    em.persist(member);
    
    //1차 캐시에서 조회
    Member findMember = em.find(Member.class, "member1");

    persist를 통해 엔티티를 영속 컨텍스트에 저장하면 1차 캐시에 저장되며, find를 통해 조회할 때 캐시를 통해 조회하게 됩니다.

     

     

    DB에서 조회

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

    member2를 조회하게 되면 1차 캐시에 없기 때문에 DB에서 조회(SELECT문 실행)를 해서 1차 캐시에 저장하게 됩니다. 이후 member2를 반환합니다.

     

    이처럼 1차 캐시를 통해 DB에서의 접근을 줄일 수 있게 됩니다.

     

     

    2. 영속 엔티티의 동일성 보장

    1차 캐시를 통해 영속 엔티티의 동일성을 보장해줍니다. 좀 더 자세하게는 1차 캐시를 통해 REPEATABLE READ를 가능하게 만들어줍니다. (이는 어플리케이션 차원에서 지원해주는 것)

    Member a = em.find(Member.class, "member1"); 
    Member b = em.find(Member.class, "member1");
    
    System.out.println(a == b); //동일성 비교 true

     

     

    3. 트랜잭션을 지원하는 쓰기 지연(Transactional write-behind)

    JPA는 엔티티 등록 시 트랜잭션을 지원하는 쓰기 지연을 제공합니다. 트랜잭션 내에서의 persist를 호출할 때마다 INSERT SQL을 데이터베이스에 보내는 게 아니라 커밋하는 순간 데이터베이스에 INSERT SQL을 보냅니다.

    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    //엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
    
    transaction.begin(); // [트랜잭션] 시작
    em.persist(memberA);
    em.persist(memberB);
    //여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
    
    //커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
    transaction.commit(); // [트랜잭션] 커밋

    사실 영속 컨텍스트에는 쓰기 지연 SQL 저장소라는 것도 존재하고 있습니다. em.persist(memberA)를 호출 시 1차 캐시에 memberA를 저장함과 동시에 INSERT memberA SQL을 쓰기 지연 SQL 저장소에 동시에 저장합니다. 이후 memberB도 같은 방식으로 1차 캐시와 동시에 쓰기 지연 SQL 저장소에 SQL을 저장합니다.

     

    그렇게 쓰기 지연 SQL 저장소에 쌓인 SQL은 DB의 commit이나 flush함수 호출 시 실행되게 됩니다. SQL이 실행된 이후 DB에 값이 저장되게 됩니다. 

     

    이처럼 쓰기 지연 SQL 저장소를 통해 버퍼링을 가능하게 합니다.

     

     

    4. 변경 감지

    영속성 컨텍스트를 통해 변경을 감지할수도 있습니다.

     

    영속 상태의 엔티티는 값을 변경한 후 update와 같은 작업이 없어도, flush나 commit 시점에 엔티티와 맨 처음 영속 컨텍스트에 들어올 때 생긴 스냅샷의 비교를 통해 영속 상태의 변경이 있을 시 엔티티 값을 변경하는 UPDATE 쿼리를 DB에 날리게 됩니다. 값을 변경했으니 update 작업이 필요하지 않냐는 의견이 있는데 결론적으로는 필요없습니다.

    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    
    transaction.begin(); // [트랜잭션] 시작
    
    // 영속 엔티티 조회
    Member memberA = em.find(Member.class, "memberA");
    
    // 영속 엔티티 데이터 수정
    memberA.setUsername("hi");
    memberA.setAge(10);
    
    //em.update(member) 이런 코드가 있어야 하지 않을까? NO
    transaction.commit(); // [트랜잭션] 커밋

     

    추가로 엔티티의 삭제는 remove함수를 통해 진행되며 매커니즘은 위 수정과 동일합니다..

    //삭제 대상 엔티티 조회 
    Member memberA = em.find(Member.class, “memberA");
    em.remove(memberA); //엔티티 삭제

    다음은 플러시와 준영속에 대해 자세하게 알아보겠습니다.

     

    해당 강의는 Infrean 김영한님의 자바 ORM 표준 JPA 프로그래밍 강의를 기반으로 작성됐습니다.

    댓글

Designed by Tistory.