[JPA] 영속성 관리

조성우·2024년 3월 18일

JPA - 공부

목록 보기
3/16
post-thumbnail

JPA에서 가장 중요한 2가지

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

엔티티 매니저

  • 웹 어플리케이션에서 요청이 들어올 경우, 엔티티 매니저 팩토리는 고객의 요청이 들어올 때마다 엔티티 매니저를 생성하고, 엔티티 매니저는 DB 커넥션 풀을 사용하여 DB에 접근함

영속성 컨텍스트

  • 엔티티를 영구 저장하는 환경
  • 논리적인 개념으로 눈에 보이지 않음
  • EntityManager.persist(entity) : 엔티티를 영속성 컨텍스트에 저장

엔티티의 생명주기

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

    //객체를 생성한 상태(비영속)
    Member member = new Member();
    member.setId("member1");
    member.setUsername("회원1");
  • 영속(managed): 영속성 컨텍스트에 관리되는 상태

    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    
    //객체를 저장한 상태(영속)
    em.persist(member);
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태

    //회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
    em.detach(member);
    
    // 아래 코드로도 준영속 상태를 만들 수 있음
    em.clear() : 영속성 컨텍스트를 완전히 초기화
    em.close() : 영속성 컨텍스트를 종료
  • 삭제(removed): 삭제된 상태

    //객체를 삭제한 상태(삭제)
    em.remove(member);

영속성 컨텍스트의 이점

  • 1차 캐시

    //엔티티를 생성한 상태(비영속)
    Member member = new Member();
    member.setId("member1");
    member.setUsername("회원1");
    
    //1차 캐시에 저장됨
    em.persist(member);
    
    //1차 캐시에서 조회
    Member findMember = em.find(Member.class, "member1");


    (그러나 엔티티 매니저는 트랜잭션 단위로 존재하므로 어플리케이션 전체에서 공유하는 캐시인 2차 캐시와 달리 성능 상의 이점이 크지 않음)

  • 동일성(identify) 보장

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

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

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

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


  • 변경 감지(Dirty Checking)

    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) 이런 코드가 있어야 하지 않을까?
    
    transaction.commit(); // [트랜잭션] 커밋

  • 지연 로딩(Lazy Loading)
    (중요한 개념이므로 나중에 따로 다뤄봐야겠다.)


플러시

: 영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것


플러시가 발생하는 과정
1. 변경 감지
2. 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

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

  • em.flush(): 직접 호출
  • 트랜잭션 커밋: 플러시 자동 호출
  • JPQL 쿼리 실행: 플러시 자동 호출

JPQL 쿼리 실행시 플러시가 자동으로 호출되는 이유

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

//중간에 JPQL 실행
query = em.createQuery("select mㅌ from Member m", Member.class);
List<Member> members= query.getResultList();

객체를 영속화 시킨 직후 해당 객체와 관련된 JPQL을 실행하면 플러시가 자동 호출되어 변경내용이 반영된 데이터베이스의 데이터를 가져올 수 있음

플러시는 영속성 컨텍스트를 비우는 것이 아니라 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화시키는 것이며, 이러한 매커니즘이 가능한 이유는 트랜잭션이라는 작업 단위의 존재 때문이다. 즉, 커밋 직전에만 동기화하면 되기 때문에 가능한 것이다.

0개의 댓글