백엔드/JPA

[ JPA ] 6-2. Entity 캐시

Reference. 한 번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 Online

이전 글

1. EntityManager

  • 실질적인 쿼리를 실행하는 역할 (persist(), merge(), remove() 등)
  • Spring Data JPAEntityManager를 래핑해서 쉽게 사용
    • 내부적인 실제동작은 EntityManager를 통해 실행
  • 기능 추가, 성능 이슈 등 커스텀이 필요한 경우 EntityManager를 직접 받아 처리
  • Hibernate에서 제공하는 SessionImpl 구현체 사용(HibernateEntityManagerImplements)
  • Hibernate에서 EntityManagerSession이라고 사용

2. EntityManager cache (1차 캐쉬)

  • 1차캐쉬는 Map의 형태 (key: id, value: Entity)
  • 실행순서
    1. 1차 캐쉬 조회

      2-1. 값이 있는 경우 리턴

      2-2. 값이 없는 경우 DB 조회 후 1차 캐쉬 저장 후 리턴

  • Id값을 통해 1차 캐쉬를 사용할 수 있음 (다른 컬럼 조회에선 캐쉬는 캐쉬적용이 안됌)
    Id값을 이용한 1차 캐쉬
    @SpringBootTest
    @Transactional //실행로직을 하나의 Transaction으로 묶기 위함
    public class EntityManagerTest {
    
    		@Test
        void cacheFindTest(){
            System.out.println(userRepository.findById(1L).get());
            System.out.println(userRepository.findById(1L).get());
        }
    }
    //결과
    //DB에는 1번만 조회 후 캐쉬에서 값을 조회
    
    Hibernate: 
        select
            user0_.id as id1_7_0_,
            user0_.created_at as created_2_7_0_,
            user0_.updated_at as updated_3_7_0_,
            user0_.email as email4_7_0_,
            user0_.gender as gender5_7_0_,
            user0_.name as name6_7_0_,
            userhistor1_.user_id as user_id6_8_1_,
            userhistor1_.id as id1_8_1_,
            userhistor1_.id as id1_8_2_,
            userhistor1_.created_at as created_2_8_2_,
            userhistor1_.updated_at as updated_3_8_2_,
            userhistor1_.email as email4_8_2_,
            userhistor1_.name as name5_8_2_,
            userhistor1_.user_id as user_id6_8_2_ 
        from
            user user0_ 
        left outer join
            user_history userhistor1_ 
                on user0_.id=userhistor1_.user_id 
        where
            user0_.id=?
    
    User(super=BaseEntity(createdAt=2021-08-07T09:09:06, updatedAt=2021-08-07T09:09:06), id=1, name=martin, email=martin@fastcampus.com, gender=null)
    User(super=BaseEntity(createdAt=2021-08-07T09:09:06, updatedAt=2021-08-07T09:09:06), id=1, name=martin, email=martin@fastcampus.com, gender=null)
    Id값이 아닌 컬럼의 조회 (1차 캐시 적용 X)
    @Test
    void cacheFindTest(){
    	  System.out.println(userRepository.findByEmail("martin@fastcampus.com"));
        System.out.println(userRepository.findByEmail("martin@fastcampus.com"));
    }
    Hibernate: 
        select
            user0_.id as id1_7_,
            user0_.created_at as created_2_7_,
            user0_.updated_at as updated_3_7_,
            user0_.email as email4_7_,
            user0_.gender as gender5_7_,
            user0_.name as name6_7_ 
        from
            user user0_ 
        where
            user0_.email=?
    
    User(super=BaseEntity(createdAt=2021-08-07T09:23:55, updatedAt=2021-08-07T09:23:55), id=1, name=martin, email=martin@fastcampus.com, gender=null)
    
    Hibernate: 
        select
            user0_.id as id1_7_,
            user0_.created_at as created_2_7_,
            user0_.updated_at as updated_3_7_,
            user0_.email as email4_7_,
            user0_.gender as gender5_7_,
            user0_.name as name6_7_ 
        from
            user user0_ 
        where
            user0_.email=?
    
    User(super=BaseEntity(createdAt=2021-08-07T09:23:55, updatedAt=2021-08-07T09:23:55), id=1, name=martin, email=martin@fastcampus.com, gender=null)
  • @Transactional을 통해 쓰기 지연이 가능
    • 쓰기지연: 최대한 DB에 반영하는 시기를 늦춰서 반영
      • 자체적으로 Entity 값을 merge하고 반영
      • DB에 접근 횟수가 낮아져 성능향상이 가능 (반영할 쿼리 모으고 한번에 적용)
    • save(), delete(), update() 를 실행하는 시점에 DB반영하지 않음
    • 실행로직들이 Transaction으로 묶여있지 않다면 그때마다 반영
      • save() 등의 메소드는 자체 @Transactional 로 묶여있음
      • 메소드, 클래스@Transactional로 선언하지 않으면 각자 메소드가 독립적 실행
      @Transactional
      @Override
      public <S extends T> S save(S entity) {
      ...
  • 영속성 컨텍스트에 데이터를 DB반영하는 방법 (동기화 시점)
    1. flush() 를 사용 (개발자의 의도적 사용)
      • 남발하면 영속성 컨텍스트에 장점을 모두 잃음
    1. Transaction commit이 발생하는 경우
      • AutoFlush가 발생
      • Test에선 마지막에 commit하지 않고 rollback하기 때문에 로직 실행X
    1. JPQL이 실행될 경우
      • AutoFlush가 발생
      • select * from user 를 통해 조회 쿼리를 실행
      • 영속성 컨텍스트에만 수정데이터가 반영되있기 때문에 DB와 값이 다른 현상 발생
        • AutoFlush를 통해 값을 동기화시키고 반영된 값까지 조회
        @Test
        void cacheFindTest2() {
            User user = userRepository.findById(1L).get();
            user.setName("marrrrrrrrrtin");
            userRepository.save(user);
        
            System.out.println("-------------------------------------------------");
        
            user.setEmail("martin@gmail.com");
            userRepository.save(user);
        
            System.out.println(userRepository.findAll());
        }
        ...
        
        -------------------------------------------------
        --flush()가 없지만 update가 실행 됌
        
        Hibernate: 
            update
                user 
            set
                updated_at=?,
                email=?,
                gender=?,
                name=? 
            where
                id=?
        
        ...