JPA

[JPA] 영속성 컨텍스트와 Flush

Empty Brain 2022. 12. 22. 19:05

나다.

 

 

이 글이 뭐하는 글이냐면

저번 글에서 영속성 컨텍스트의 개념과 엔티티의 상태에 대해 알아봤으니 이제

실질적으로 persist를 통해 엔티티를 영속성 컨텍스트에 집어넣었을 때 일어나는 동작들과

영속성 컨텍스트의 부속적인 요소 등에 대해 알아보려 한다.

 


그래서 영속성 컨텍스트의 특징과 장점은?

1. 영속 상태의 엔티티는 무조건 식별자 값을 가지고 있어야 한다.

 - JPA는 식별자 값을 통해 컨텐츠를 구분한다. 없으면 안됨.

 

2. Flush를 통한 데이터베이스와 영속성 컨텍스트의 동기화.

 - Flush를 통해 영속성 컨텍스트에 일어난 변화를 데이터베이스와 동기화한다.

 

3. 1차 캐시

캐시데이터 라는 말을 들어본 적이 있는가. 

우리가 자주 가는 웹사이트들은 어쩐지 로딩이 존X 빠르다고 느끼며

"오 역시 머기업 속도가 15G구요."

라고 생각한 적이 있을 것이다.

 

우리가 웹사이트를 접속할 때 브라우저는 캐시데이터 라는 것을 저장한다.웹사이트를 랜더링하기 위해 필요한 리소스들을 미리 저장해두고 사용자가 사이트를 재방문 할 때미리 준비되어있던 리소스를 건내줌으로 로딩시간을 줄이는 것이다.

 

JPA 또한 캐시를 사용할 수 있다.영속성 컨텍스트에는 캐시가 있는데 우리는 이것을 1차 캐시라 부르기로 했다.영속 상태의 엔티티를 저장하고 식별자 값"키"로 값은 엔티티의 인스턴스로가져오게 된다.

하지만 1차 캐시의 경우 하나의 트랜젝션 안에서만 사용되고 트랜젝션이 소멸하며 같이 없어지기 때문에

큰 이점을 가져간다 생각하긴 어렵다.

 

캐시에 무언가를 저장했다는 것은 그 용도가 조회라는 것일텐데

이것을 조회하기 위해서 어떤 방식을 사용해야 할까?

이 또한 EntityManager가 해결해준다.

@Repository
@RequiredArgsConstructor
public class MemberRepository {

    private final EntityManager em;

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }
}

em.find라는 문법을 통해 내가 원하는 식별자 값을 가진 엔티티를 찾게 되는데

Member 타입을 가진 파라미터로 넘어온 id값 n번의 엔티티를 찾아오라는 뜻이다.

 

1번에서 언급한 식별자 값이 무조건적으로 필요한 이유가 나온다.

다음의 표를 보도록하자.

이름 소속
김시발 느그초등학교 3반
김시발 느그초등학교 3반

당신은 이 표를 보고 김시발 어린이를 구분할 수 있는가.

아마 없을 것이다.

 

하지만 여기서 만약 느그초등학교 3반은 키순으로 학번을 정한다는 룰이 있다면

학번 이름 소속 특징
1 김시발 느그초등학교 키 233cm 임.
2 김시발 느그초등학교 키110cm 임.

룰로 인해 학번이 명확하게 정해진다면 김시발 어린이를 명확하게 구분할 수 있는 지표가 된다.

이게 식별자 값이 무조건적으로 있어야하는 이유다.

 

어쨋든, 1차 캐시를 통해 먼저 조회하고 결과를 돌려주기 때문에 속도에 더 최적화되어있다.

 

"그러면 1차 캐시에 없으면 못가져오는거 아님? ㅋㅋ ㅄ"

시X 안됬으면 JPA는 진작에 유기당했다.

이 기술은 난다긴다하는 석박들과 고이다 썩은 개발자들에 의해 탄생한 기술이다.

그런 기술적 안배를 하지 않았을 가능성은 당연히 나와 당신이 여자친구를 사귈 확률

수렴한다고 생각하면 된다.

 

1차 캐시에 만약 존재하지 않는다면 데이터베이스를 통해 조회하게 된다.

그리고 다음 조회를 위해 엔티티를 생성해 1차 캐시에 박아버린다.

한마디로 다음 조회의 속도를 보장하기 위해 영속상태로 만들어버린다는 뜻이다.

"너 저장된거야"

 

그 다음 조회된 결과를 반환하게 된다.

 

 

4. 동일성 보장

 

엔티티의 동일성을 보장한다는 말은 무엇인가.

몇 번을 꺼내오든 같은걸 꺼내온다는 뜻이다.

 

예를 들어 비교해보겠다.

public void checkEntity(Long id){
	Member memberA = em.find(Member.class , id);
    	Member memberB = em.find(Member.class , id);
    
    	if(memberA == memberB){
   		System.out.println("같음");
    	 } else
     		System.out.println("다름");
}

위의 코드는 어떤 결과를 내놓을 것 같은가.

정답은 "같음" 이다.

이 것은 .equals()를 통해 비교해도 같다.

인스턴스와 가지고 있는 값 모두 memberA, memberB를 통해 두 번 호출해도 같다는 뜻이다.

왜냐면 식별자 값을 통해서 1차 캐시에서 같은 인스턴스를 땡겨온 애들이니까.

 

 


Flush가 무엇일까.

대부분 플러시라고 한다면 이게 떠오를 가능성이 있다.

도박중독 신고는 국번없이 1336

 

JPA의 세계에서 Flush라 함은 트랜젝션 커밋를 통해 자동으로 호출되는 일련의 처리과정을

부르는 말이다.

먼저 이 Flush 동작으로 인해 데이터가 데이터베이스에 저장되는 것은 아니다.

Flush와 Commit은 엄연히 다른 행위로 구분된다.

 

먼저 순서도로 동작을 보도록 하자.

변경 감지의 동작순서
 
트랜젝션 커밋  → 더티 체킹(변경 감지) → 내부 쿼리 저장소(쓰기 지연 저장소)에 쿼리 생성 →
         내부 쿼리 저장소의 쿼리 DB로 이동(플러시) → DB에서 쿼리 수령 후 변경사항 반영
(위의 과정을 통해 영속성 컨텍스트의 변경사항을 DB에 반영하게 됨.)

 

트랜젝션 커밋은 수동으로 할 수 있는데 EntityManager가 자동으로 해주니 이건 필요하면

구글에 찾아보시기 바란다.

 

트랜젝션 커밋이 일어나는 순간부터

먼저 더티 체킹이 일어나는데

더티 체킹은 영속성 컨텍스트 내부의 엔티티에 변화가 있는지 스냅샷과 대조하여 검증하는

절차를 말한다.

 

만약 변경된 엔티티가 존재한다면 내부 쿼리 저장소에 쿼리를 생성해 저장하고

해당 내부 쿼리 저장소의 내용을 데이터베이스에 전송한다.

해당 과정을 플러시라고 부른다.

 

사실 변경 감지와 플러시를 구분지어 설명한다는게 참 어려운 것 같다.

그냥 플러시라는 동작 자체가 변경 감지의 범주에 속해있다고 이해하는 것이 편할 듯 한데

이렇게 이해하는 것이 맞는지는 모르겠다.

ㅈㅅ! ㅋㅋ

 

 

어쨋든 영속성 컨텍스트에 대한 내용은 이정도로 마무리하려 한다.

뭐 더 좋은게 있다면 뛰어와서 적겠지만 말이다.

 

혹여나 글에 틀린 점이 있다면 그건 내가 허접이라 그런 것이니 너른 양해를 부탁드린다.

 

그럼 컴파일 오류 없는 개발 하시길 :)