당니의 개발자 스토리

플러시 본문

플러시

자 그럼 플러시라는 게 뭐냐?

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 거예요. 보통 데이터베이스 트랜잭션이 딱 커밋될 때 플러시라는 게 일어나는데요. 그러니까 플러시라는 게 막 복잡한 게 아니라, 우리 아까 쌓아놨던 insert SQL이나, delete SQL이나, update SQL 같은 게 데이터베이스에 날라가는 거예요.

그러니까 쉽게 얘기해서 영속성 컨텍스트의 현재 변경 사양과 그 데이터베이스를 딱 맞추는 작업이라고 보시면 돼요. 그래서 영속성 컨텍스트에 쿼리들을 DB에 쫙 날려주는 거에요.

그럼 플러시가 발생하면 무슨 일들이 생기냐.

아까 여러분께 말씀드렸던 데이터베이스 트랜잭션이 커밋되면 플러시가 자동으로 발생한다고 보시면 되구요.

자 그럼 먼저 Dirty Checking, 변경 감지가 일어나고요. 그러면서 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록합니다.

그리고 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송하는데, 이 쓰기 지연 SQL 저장소에는 등록 쿼리, 수정 쿼리, 삭제 쿼리 등이 다 담겨 있겠죠. 이 쿼리를 데이터베이스에다가 쫙 보내는 겁니다. 플러시가 발생한다고 해서 데이터베이스 트랜잭션이 커밋되는 건 아니고요. 쿼리를 보내고 그 다음에 데이터베이스 트랜잭션을 커밋합니다.

그러면 영속성 컨텍스트를 어떻게 플러시 하냐? 하면 사실 이걸 직접 쓰실 일은 거의 없어요. 근데 일단 알아놔야 돼요. 나중에 뭐 테스트 해보거나 하시려면 좀 알아두면 좋아요.

em.flush 라고 직접 호출하는 방법이 있고요. 또 트랜잭션을 커밋하면 자동으로 플러시 호출이 됩니다. 그 다음에 jpql query를 실행할 때도 자동으로 플러시가 호출이 되는데, 이거는 지금 그렇구나 라고 이해만 하시면 돼요. 지금 알아도 그게 도움이 되는 건 아니어서.

자 그럼 한번 보겠습니다.

새로운 멤버를 저장해볼게요. 그리고 얘를 em.persist 해서 저장했어요.

그런데 이게 어쨌든 트랜잭션에 커밋 되기 전까지는 이 쿼리를 볼 수가 없잖아요. 근데 내가 좀 '미리 데이터베이스에 반영을 하고 싶어' 라던가, '쿼리가 날라가는 거를 좀 미리 보고 싶어' 그러면,

em.flush 라고 해서 이걸 강제로 출하시면 됩니다. 그러면 방금 일어났던 플러시라는 메커니즘이 지금 바로 즉시 일어납니다.

이렇게 하면 데이터베이스에 insert 쿼리가

이 시점에 즉시 나가고,

그 다음에 데이터베이스 트랜잭션이 커밋 됩니다.

한번 확인해 보겠습니다.

자 지금 이거 선 있죠.

이거 전에 지금 insert 쿼리가 호출이 됐어요. 무슨 말이냐면, em.persist를 한 시점에는 영속성 컨텍스트에 member가 담기고 쿼리가 딱 그 저장소에 담겨 있어요. 그런데 플러시를 강제로 해버리니까 그 쿼리가 그냥 db에 바로 반영이 되어버린거에요. 그리고 트랜잭션 커밋. 이렇게 돌아가게 됩니다.

그런데 간혹 이런 질문들을 하세요. '혹시 플러시를 하게 되면 1차 캐시가 다 지워지나요?' 아닙니다. 1차 캐시는 그대로 다 유지가 되고요. 플러시라는 것은 1차 캐시를 지우거나 이런 게 아니에요. 플러시에서 막 데이터를 다 보내는 게 아니라,

오직 영속성 컨텍스트에 있는 쓰기 지연 SQL 저장소에 있는 쿼리들,

그리고 이제 변경 감지가 일어나고 쓰기 지연 SQL 저장소에 쿼리들이 쌓이고, 그러니까 등록, 수량, 삭제 쿼리들, 뭔가 바뀐 것, 이것들이 그냥 데이터베이스에 반영이 되는 과정이라고 보시면 됩니다.

일단 jpql query를 실행할 때 플러시가 자동으로 호출되는 이유는 그냥 간단하게 설명 드릴게요.

예를 들어서 지금 멤버 A, B, C를 저장을 했단 말이에요. 근데 이때까지 실제 데이터베이스에 이 query가 날라갈까요? 안 날라간다고 설명 드렸습니다. 멤버 A, B, C를 데이터베이스에 넣은 다음에 바로 그 아래 코드에서 jpql로 모든 멤버를 조회를 해와요. 그럼 이 멤버 A, B, C가 데이터베이스에서 조회가 될까요, 안될까요?

안되죠. 왜냐? DB에 쿼리라도 날라가야 DB에서 저장이 되어있으니 가져올 수 있을텐데, 지금 DB에 A, B, C를 insert하는 쿼리 자체가 안 나간거에요. 근데 JPQL은 그냥 SQL로 번역이 돼서 실행이 되거든요. DB에서 가져올게 없는거죠. 그래서 잘못하면 문제 생긴단 말이에요. 그래서 JPA는 이런 걸 방지하고자 기본 모드가 JPQL 쿼리를 실행할 때는 무조건 플러시를 날려버립니다.

그리고 나서, jpql 쿼리가 날라가니까 A, B, C가 조회가 되는거죠.

이런 게 있다고 이해만 하시면 돼요. 그러면 나중에 실전에서 아 그래서 날라가는구나 라고 이해하시면 됩니다.

자 이제 플러시 모드 옵션이 있는데요.

em.setFlushMode 라고 해서 세팅을 하는데, 사실 여러분 쓰실 일은 없습니다. 기본적으로 auto 라고 되어 있는데요. 방금 설명한 것처럼 내가 트랜잭션을 커밋 하거나, 쿼리를 실행할 때 플러시를 하고 커밋하고 플러시를 하고 쿼리를 실행하는 겁니다. 이게 이제 기본 값이구요.

커밋 모드는 뭐냐면 커밋을 할 때만 플러시가 일어납니다. 무슨 얘기냐면 쿼리를 실행할 때는 플러시를 안 한다는 거예요.

'이게 왜 있지?' 가끔 도움이 될 때가 있습니다. 뭐냐면,

예를 들어서 이 경우에 이 코드에서 만약에 어쨌든 플러시를 하면 db에 일단 쿼리가 나가버리잖아요.

그러니까 나는 지금 막 db에 쿼리 날릴 필요 없는 거예요. 예를 들어서 지금 pdf에서는 jpql에서 select로 Member를 조회하고 있는데, Member가 아니라 전혀 다른 테이블, 상품을 가지고 온다고 해봐요. select Item 이라고 하면 그럼 굳이 em.persist(member)를 지금 당장 플러시할 필요는 없겠죠.

그래서 이런 경우에는 정 원하면 플러시 모드를 커밋으로 바꿔놓으면 된다 라는 건데 크게 도움이 되지 않습니다.

여러분께 말씀드리고 싶은 건 그냥 AUTO로 쓰십시오. default가 AUTO라서 가급적 손 대지 마시고 쓰시는 걸 권장드립니다.

자 그래서 이제 플러시라는 것은 영속성 컨텍스트를 비우는 게 아니다. 이거 꼭 조심하셔야 됩니다.

그리고 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 게 플러시라고 이해하시면 됩니다.

그리고 이 플러시라는 메커니즘이 동작할 수 있는 이유는 사실 이 트랜잭션이라는 작업 단위가 있기 때문이에요. 데이터베이스 트랜잭션이라는 개념이 있기 때문에 가능한 거고, 결국 아까 JPA에서 insert를 모아서 하든 update를 나중에 천천히 날리든, 중요한 건 커밋 직전에만 동기화를 하면 되는 거예요. 어쨌든 트랜잭션 커밋 직전에만 변경 내용을 DB에 날려주면 되기 때문에 이런 메커니즘들이 다 가능한 거예요.

JPA는 기본적으로 어떤 데이터를 맞추거나, 이런 동시성에 대한 거는 다 데이터베이스 트랜잭션에 위임해서 쓰거든요. 참고로 알아두시면 될 것 같습니다.

 

이제 플러시는 끝났구요. 다음 시간에 봅시다.

'스프링 > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글

정리  (0) 2024.05.26
준영속 상태  (0) 2024.05.26
영속성 컨텍스트 2  (0) 2024.05.26
영속성 컨텍스트 1  (0) 2024.05.25
Hello JPA - 애플리케이션 개발  (0) 2024.05.25