[Spring] Entity Cache

WOOK JONG KIM·2022년 11월 21일
0

패캠_java&Spring

목록 보기
59/103
post-thumbnail

Entity Manager

영속성 컨텍스트안에서 엔티티는 조회,생성,제거 됨 -> 영속성 컨텍스트 내에서 엔티티를 관리하는 것이 Entity Manager

실질적인 쿼리를 실행하는 역할 (persist(), merge(), remove() 등)

Spring Data JPA는 EntityManager를 래핑해서 쉽게 사용

내부적인 실제동작은 EntityManager를 통해 실행

기능 추가, 성능 이슈 등 커스텀이 필요한 경우 EntityManager를 직접 받아 처리

Hibernate에서 제공하는 SessionImpl 구현체 사용(HibernateEntityManagerImplements)

Hibernate에서 EntityManager를 Session이라고 사용

Cache를 가짐

Entity Cache

Cache란 자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 장소

실제로 우리가 save 같은 메서드 실행 시점에 db에 반영되지 않고 캐시 가짐
-> 우리가 사용하는 영속성 컨텍스트와 디비 사이에 갭이 발생한다는 의미

1차 cache

1차캐쉬는 Map의 형태 (key: id, value: Entity)

실행순서

  • 영속성 컨텍스트 내 1차 캐쉬 조회
  • 2-1. 값이 있는 경우 리턴
  • 2-2. 값이 없는 경우 DB 조회 후 1차 캐쉬 저장 후 리턴

Id값을 통해 1차 캐쉬를 사용할 수 있음 (다른 컬럼 조회에선 캐쉬는 캐쉬적용이 안됌)

@Transactional //실행로직을 하나의 Transaction으로 묶기 위함
...

@Test
    void cacheFindTest(){

        System.out.println(userRepository.findByEmail("martin@fastcampus.com"));
        System.out.println(userRepository.findByEmail("martin@fastcampus.com"));
        System.out.println(userRepository.findByEmail("martin@fastcampus.com"));

        System.out.println(userRepository.findById(1L).get());
        System.out.println(userRepository.findById(1L).get());
        System.out.println(userRepository.findById(1L).get());
    }

findByEmail만 사용했을 경우 1차 캐시가 적용되지 않아 select 문 3번 수행 하는 반면

findById만 사용했을 경우 처음 한번 select 쿼리 실행 후에는 캐쉬만을 조회하여 리턴하게 됨

둘다 한번에 실행 시 findByEmail에서 캐시가 적용하였기에 밑에 findById는 쿼리 문 실행 없이 바로 값을 가져옴

개발자가 findById를 통해 값을 찾아오는 경우는 드물지만 JPA 내부적으로 로직을 처리할때는 사용하는 경우가 많음(이때 성능 저하를 막기 위해 1차 캐시 사용)

ex) userRepository.deleteById(1L);

이 경우 select 문을 실행 후 delete를 진행 함
-> 이때 쓰기 지연의 역할을 하는 @Transactional 어노테이션 지정 후 위 코드 실행 시 select 쿼리만 실행하고 delete 쿼리는 진행하지 않음
-> select 이후 롤백이 진행되어 영속성 컨텍스트 내에서만 존재하지만 디비로 전달되지 않음

@Transactional

쓰기지연: 최대한 DB에 반영하는 시기를 늦춰서 반영

자체적으로 Entity 값을 merge하고 반영

DB에 접근 횟수가 낮아져 성능향상이 가능 (반영할 쿼리 모으고 한번에 적용)

save(), delete(), update() 를 실행하는 시점에 DB반영하지 않음

실행로직들이 Transaction으로 묶여있지 않다면 그때마다 반영

save() 등의 메소드는 자체 @Transactional 로 묶여있음

	@Transactional
	@Override
	public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null.");

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

메소드, 클래스에 @Transactional로 선언하지 않으면 코드 한줄 한줄이 각자의 트랜잭션을 가지고 실행된다고 생각 -> 즉각 디비 반영

반면 선언 시에는 코드 전체가 하나의 트랜잭션이 된다

	@Test
    void cacheFindTest2(){
        User user = userRepository.findById(1L).get();
        user.setName("marrrrtin");

        userRepository.save(user);

//        userRepository.flush(); -> 영속성 컨텍스트에 쌓여있는 데이터를 디비에 반영
        // 개발자가 원하는 시점에 디비에 영속화를 하고자 할 때 사용
        // 남용시 캐시에 장점이 사라지겠지?

        System.out.println("--------------------");

        user.setEmail("marrrrtin@fastcampus.com");

        userRepository.save(user);

        userRepository.flush();
    }

위 코드의 상위 테스트 클래스에 @Transactional을 선언하지 않으면 update 쿼리 두번 실행 -> 위 경우엔 캐시를 이용하여 1번

반면 주석 처리한 userRepository.flush() 사용 시 디비에 반영되면서 캐시가 사라지므로 update 쿼리 두번 실행

update 한번 수행 -> userHistroy 관련 쿼리도 두번 아닌 한번만 실행됨!!!

영속성 컨텍스트에 데이터를 DB반영하는 방법 (동기화 시점)

  1. flush() 를 사용 (개발자의 의도적 사용)
    -> 남발하면 영속성 컨텍스트에 장점을 모두 잃음

  2. Transaction이 끝나면서 Transaction commit이 발생하는 경우 AutoFlush가 발생
    -> Test에선 마지막에 commit하지 않고 rollback하기 때문에 업데이트 쿼리 실행 X

  3. JPQL(select * from user)가 실행될 경우
    AutoFlush가 발생

영속성 컨텍스트에만 수정데이터가 반영되있기 때문에 DB와 값이 다른 현상 발생-> 밑에 FindAll()시 AutoFlush를 통해 값을 동기화시키고 반영된 값까지 조회

배우기 전엔 당연히 update 쿼리가 반영되지 않은 값이 프린트 될것이라 생각

	@Test
    void cacheFindTest2(){
        User user = userRepository.findById(1L).get();
        user.setName("marrrrtin");
        userRepository.save(user);

        System.out.println("--------------------");

        user.setEmail("marrrrtin@fastcampus.com");
        userRepository.save(user);

        System.out.println(userRepository.findAll()); //select * from user
    }
...

-------------------------------------------------
--flush()가 없지만 update가 실행 됌

Hibernate: 
    update
        user 
    set
        updated_at=?,
        email=?,
        gender=?,
        name=? 
    where
        id=?

...
profile
Journey for Backend Developer

0개의 댓글