[JPA] 3. 영속성 관리 - 내부 동작 방식

최진민·2021년 6월 4일
0

JPA

목록 보기
3/11
post-thumbnail

JPA 중요 2가지

  1. 객체 & 관계형 DB 매핑(ORM) - 정적
  2. 영속성 컨텍스트 - 내부 동작 관련

영속성 컨텍스트 1

  • Entity Manager Factory & Entity Manager

  • 영속성 컨텍스트

    • "엔티티를 영구 저장하는 환경"
    • EntityManager.persist(entitiy)
      • .persist()는 DB에 저장하는 것이 아닌 영속성 컨텍스트에 저장하는 것!
      • 이 후, 트랜잭션 커밋 시점에 영속성 컨텍스트 → DB 반영
    • (Java to SE환경) 엔티티 매니저와 영속성 컨텍스트가 1:1
  • 엔티티 생명주기

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

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

      //객체를 생성한 상태(비영속)
      Member member = new Member();
      member.setId("member1");
      member.setUsername(“회원1);
      
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      
      //객체를 저장한 상태(영속)
      em.persist(member);
    • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태

      //회원 엔티티를 영속성 컨텍스트에서 분리
      em.detach(member);
    • 삭제(removed) : 삭제된 상태

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

영속성 컨텍스트 2

  • 영속성 컨텍스트 장점

    • 1) 1차 캐시

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

      • 조회 시, 먼저 DB에서 찾는게 아니라 1차 캐시에서 조회.

        //1차 캐시에서 조회
        Member findMember = em.find(Member.class, "member1");

      • 그 다음 조회할 때? (있으면 1차 캐시, 없으면 DB)

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

    • 2) 동일성(identity) 보장

      • 영속 엔티티 동일성 보장

        Member a = em.find(Member.class, "member1");
        Member b = em.find(Member.class, "member1");
        
        System.out.println(a == b); //동일성 비교 true
        • 1차 캐시로 REPEATABLE READ 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
    • 3) 트랜잭션을 지원하는 쓰기 지연(Transactional write-behind)

      EntityManager em = emf.createEntityManager();
      EntityTransaction transaction = em.getTransaction();
      //엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
      transaction.begin(); // [트랜잭션] 시작
      
      em.persist(memberA); //1)
      em.persist(memberB); //2)
      //여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
      //Just, 영속성 컨텍스트 저장
      
      //트랜잭션 커밋 시점, 데이터베이스에 INSERT SQL을 보낸다.
      transaction.commit(); // [트랜잭션] 커밋 3)
      • SQL(INSERT A) 쓰기 지연 저장

      • SQL(INSERT B) 쓰기 지연 저장

      • DB 반영

      • 예제)

        package hellojpa;
        
        import javax.persistence.EntityManager;
        import javax.persistence.EntityManagerFactory;
        import javax.persistence.EntityTransaction;
        import javax.persistence.Persistence;
        
        public class JpaMain {
            public static void main(String[] args) {
                EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
                EntityManager em = emf.createEntityManager();
        
                EntityTransaction tx = em.getTransaction();
                tx.begin();
        
                try {
                    //비영속
                    Member member1 = new Member(150L, "A");
                    Member member2 = new Member(100L, "B");
        
                    //영속 - DB 저장 x
                    em.persist(member1);
                    em.persist(member2);
        
                    System.out.println("==========query position line=========");
        
                    //DB에 쿼리가 날라간다.
                    tx.commit();
                } catch (Exception e){
                    tx.rollback();
                } finally {
                    em.close();
                }
        
                emf.close();
            }
        }
      • 출력

        print:
        ==========query position line=========
        Hibernate: 
            /* insert hellojpa.Member
                */ insert 
                into
                    Member
                    (name, id) 
                values
                    (?, ?)
        Hibernate: 
            /* insert hellojpa.Member
                */ insert 
                into
                    Member
                    (name, id) 
                values
                    (?, ?)
        • 💖"===query position line===" 라인 아래로 쿼리 생성
    • 4) 변경 감지(Dirty Checking)

      try {
      
                  Member member = em.find(Member.class, 150L); //name=A
                  member.setName("ZZZZ");
      
                  //em.persist(member);
      
                  tx.commit();
              }
      • .persist()가 필요 없다.

      • 결과

      • 설명

        • JPA가 Entity와 스냅샷(최초)을 일일이 비교하여 변경점에 대한 update query를 db에 날린다.
      • 엔티티 삭제

        //삭제 대상 엔티티 조회
        Member memberA = em.find(Member.class, "memberA");
        
        em.remove(memberA); //엔티티 삭제
    • 5) 지연 로딩(Lazy Loading) - 추후 설명


플러시 (Flush)

  • 플러시 : 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영

    try {
    
            Member member = new Member(200L, "member200");
            em.persist(member); //영속성 컨텍스트에 저장, DB 반영 X
    
            em.flush(); //플러시하는 순간 DB에 반영해버린다.
    
            System.out.println("===========================");
    
            tx.commit();
        }
    //앞선 설명과 다르게 em.flush()를 통해서 "=================" 위로 쿼리가 생성
  • 플러시 발생 시점

    • 변경 감지(Dirty checking)
    • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
    • 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송(등록, 수정, 삭제 쿼리)
  • 플러시하는 방법

    • em.flush() : 직접 호출

    • 트랜잭션 커밋 (tx.commit()) : 자동 호출

    • 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();
      • 💥위와 같은 코드가 있을 때, 영속성 컨텍스트 내부에 담기는 memberA, B, C는 커밋되지 않았기에 DB에 반영되지 않는다.
        • But, JPQL을 통해 DB에 반영된 내용을 조회하고 싶기 때문에 flush()를 호출한다.
  • 모드

    • em.setFlushMode(FlushModeType.COMMIT)
      • 1) FlushModeType.AUTO : 커밋이나 쿼리 실행시 플러시(기본값)
      • 2) FlushModeType.COMMIT : 커밋시에만 플러시
  • 플러시 is

    • 영속성 컨텍스트를 비우지 않고 영속성 컨텍스트의 변경 내용을 DB에 반영
    • 트랜잭션 단위가 중요 → 커밋 직전에만 동기화하자

준영속 상태(Detached)

  • 영속 → 준영속

  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)

  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

  • 준영속 상태로 만드는 방법

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

      try {
      
                  Member member = em.find(Member.class, 150L);
                  member.setName("AAAA");
      
                  em.detach(member);
      
                  System.out.println("===========================");
      
                  tx.commit();
              }
      print:
      Hibernate: 
          select
              member0_.id as id1_0_0_,
              member0_.name as name2_0_0_ 
          from
              Member member0_ 
          where
              member0_.id=?
      ===========================
      • .detach()를 준영속 상태가 됐기 때문에 "======" 이후 커밋과 동시에 보여져야 하는 UPDATE 쿼리가 출력되지 않았다. (=DB에 반영도 되지 않는다.)
    • em.clear() : 영속성 컨텍스트 초기화

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

profile
열심히 해보자9999

0개의 댓글