Rlog

Hibernate 1 차 Cache 에 대해서 알아보자 본문

Spring

Hibernate 1 차 Cache 에 대해서 알아보자

dev_roach 2022. 3. 10. 18:35
728x90

Hibernate 에서는 데이터베이스의 부하를 줄이기 위해서 1차 캐시를 이용한다.

1차 캐시란 Transaction 내에서 작동하는 캐시를 뜻한다.

이 글은 위의 1차 캐시가 어떻게 작동하는지 알기에 본다고 생각하고, Hibernate 의 코드를 분석하는데만 신경을 쓸 것이다.

 

코드는 매우 간단합니다.

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public void test() {

        User user = new User("roach");

        User save = userRepository.save(user);

        User user2 = new User(save.getId(), "dodo");

        User save2 = userRepository.save(user2);

        userRepository.findById(save.getId());
    }

}

위와 같은 Service Class 코드가 있고 해당 Transaction 에서 어떻게 1차 캐시를 생성하는지 알아볼 것입니다.

 

일단 save 메소드를 호출하게 되면 SimpleJpaRepository 의 save 메소드로 이동합니다.

여기서는 Entity 가 새로운 Entity 인지 혹은 이전에 존재했던 Entity 인지를 구분합니다.

그래서 새로운 값이라면 persist 가 작동합니다.

코드를 보면 Persist 가 작동하면서 PersistEvent 라는 것을 동작시키는데요.

이 부분을 보면서 Event-Driven 식으로 영속화와 그에 따른 관리들이 이루어진다는 것을 확인할 수 있습니다.

일단 Event 를 발행했으면 Event 를 처리해주는 Listener 를 찾아야 하는데요.

찾기 위해 DefaultPersistEventListener 라는 클래스로 이동합니다.

위의 코드를 보시면 Event 가 들어오면 해당 부분을 처리해주는 모습을 확인할 수 있습니다.

일단 위의 내용은 우리에게는 그다지 중요하지 않으니 넘어 가고 아래 코드를 쭉죽 내려가다보면 Entity 의 상태에 따라 특정 로직으로 처리하는 부분이 나옵니다.

우리가 처음으로 저장하는 Entity 이므로 아마도 해당 Entity 는 TRANSIENT 상태일 것입니다.

여기서 한가지 봐야할 사실이 있는데요. 

Debugger 의 변수중 하나인 createCache 라는 Cache 가 한가지 존재합니다.

Type 은 IdentityHashMap 이네요. 무언가 1차 캐쉬같지 않나요? ㅎㅎ

일단은 코드를 쭉쭉넘겨 entityIsTransient 부분으로 이동해 봅시다.

여기서 대략적으로 코드의 문맥만 보고 추론해보자면

Entity 의 Proxy 를 Unwrap 하고, 해당 Entity 를 createCache 에 집어 넣는 것을 확인할 수 있습니다.

일단 넣으니까 아래와 같은 형태의 (key - value) 로 저장하고 있습니다.

제가 공부했을땐 id 가 key 형태로 저장된다고 들었는데 아직까진 아닌걸로 확인됬네요.

일단 코드를 더 진행시켜 봅시다.

점점 더 들어가다 보니 performSave 라는 메소드인 Save 를 수행하는 과정이 나옵니다.

드디어 찾은거 같은데요.

영속성 컨텍스트를 가져오고 Key 는 Entity 의 @Id 를 이용합니다.

영한님의 JPA 책에서 볼 수 있던 부분이네요 ㅎㅎ

PersistenceContext 에 Id 를 이용하여 Entity 를 저장하고 있습니다.

 

이제부터가 중요합니다.

User user2 = new User(save.getId(), "dodo");

를 했을때 영속성 컨텍스트에 해당 부분이 있는지 알고 Merge 를 하는지가 중요합니다.

보시면 일단 ID 값이 존재하므로 merge 방향으로 가는 것을 확인할 수 있습니다.

아까 말했듯이 Event-Driven 으로 진행하므로 merge 때도 똑같이 Event 를 발행하는 것을 확인할 수 있습니다.

그러면 Merge 부분에서는 Listener 코드를 어떻게 구성하고 있는지 확인해봅시다.

아마도 onMerge 라는 메소드로 구성되어 있을 것 입니다.

위의 코드를 보면 정확한데 ID 가 혹시라도 null 로 들어왔을 경우 때문에 체크를 하는 느낌이다

ID 가 null 이 아닐경우 영속성 컨텍스트에서 일단 관리되고 있는 entity 를 가져온다. 

이유를 모른다면 이 글을 볼 것이 아니라 JPA 를 다시 공부해야 할 것이다.

왜냐면 이걸 모르면 nativeQuery 가 영속성 컨텍스트에 업데이트 되는지 안되는지도 모를 것이기 때문이다.

 

일단 이유를 설명해주자면 영속성 컨텍스트의 Entity Metadata 업데이트를 해줘야 하기 때문이다. 

그래서 밑의 화면을 보면 managedEntity 우리가 기존에 생성해줬던 "roach" 라는 이름으로 잘 저장되어 있음을 확인해 볼 수 있다.

근데 한가지 신기한 사실은 Entity 의 상태를 DETACHED 준영속으로 바꾼다는 것이다.

내 추측이지만 Entity 를 DETACHED 시키는 이유는 기존의 roach 를 DETACH 시키기 위함으로 생각된다.

 

그래서 DETACHED 된 상태일때 들어가는 메소드를 확인해보니

위와 같이 source 라는 곳에서 entityName 과 복사된 Indetifier(1L) 을 통해서 roach Entity 를 다시 가져온다.

따라서 result 가 Null 이 아니므로 Merge 아래의 Merge 과정을 수행하게 된다.

Merge 과정 중에 DirtyInterceptor 마크를 남겨라.. 라는 등의 메소드 등도 볼 수 있다.

역시 코드를 까봐야 프레임워크의 이해도가 높아지는 것 같다.

 

이제 마지막으로 살펴봐야 할 부분은 SELECT Query 가 나가는지 안나가는지이다.

JPA 를 많이 공부했다면 안나간다고 생각할 것이다.

일단 findById 의 경우 LoadEvent 를 발생시킨다.

따라서 우리는 onLoad Method 를 찾아야 한다.

밑의 사진을 보면 영속성 컨텍스트에서 무언가 Proxy 를 가져오는 모습을 볼 수 있다.

따라서 쿼리는 아래와 같이 작성된다.

 

나중에 더 파봐야 할점

- createCache 는 어디에 사용하는 건가?