JPA 영속성 컨텍스트에 대해 공부하던 중 flush에 대해 알게 되었다. 그래도 나름 확실히 학습했다고 생각했는데 dirty checking 테스트를 하던 중 어떤 조회 쿼리를 날리면 자동으로 flush가 이루어지고 또 다른 조회 쿼리를 날리면 flush가 이루어지지 않는 다는 것을 알게 됐다. 그래서 flush와 조회 명령의 관계에 대해 학습하기로 했다.
일단 플러시에 대해 간단히 리마인드를 해보자.
영속성 컨텍스트의 변경내용(쓰기 지연 sql 저장소의 쿼리들)을 데이터베이스에 반영하는 것
그럼 이제 본론으로 넘어가 find명령과 flush의 관계에 대해 알아보자. 전제 상황은 엔티티를 findById(1L)로 꺼내온 후, set 명령을 통해 dirty checking을 유도하는 상황이다.
@Test
void test() {
final Member member1 = members.findById(1L).get();
member1.setName("eng"); // update 쿼리가 쓰기 지연 저장소에 들어 가게 된다.
final Member member2 = members.findById(1L).get();
}
위의 상황은 객체 값 수정 후 곧 바로 findById(1L)을 또 다시 명령하는 상황이다. member1.setName("eng"); 명령을 통해 dirty checking 기능으로 update쿼리가 쓰기 지연 sql 저장소에 쌓이지 않을까 예상했지만 아니였다. 결과를 보면 update 쿼리가 찍히지 않았다. 즉, flush가 일어나지 않았다는 것이다. flush가 발생할 때 dirty checking이 실행되기 때문이다. 아래 코드 처럼 수동으로 flush를 해주면 update 쿼리가 찍힌다.
@Test
void test() {
final Member member1 = members.findById(1L).get();
member1.setName("eng");
final Member member2 = members.findById(1L).get();
members.flush(); //수동 flush
}
참고
두 번의 findById(1L)명령을 했지만 쿼리가 한 번만 나가는 이유는 영속성 컨텍스트의 1차 캐시 때문이다.
그럼 이어서 관련이 없는 findById(2L) 명령을 해보자. 어떤 결과가 나올까? update쿼리는 수동 flush 명령을 하지 않으면 찍히지 않을 것으로 예상된다.
@Test
void test() {
final Member member1 = members.findById(1L).get();
member1.setName("eng");
final Member member2 = members.findById(2L).get();
}
예상대로 찍히지 않는 다는 것을 확인할 수 있다.
참고
위에서는 두 번의 select 쿼리가 나가는데 두 번 다 원하는 엔티티가 1차 캐시에 존재하지 않기 때문이다. 그렇기에 2차적으로 db를 찌르게 되어 쿼리문이 두 번 실행되는 것이다.
그럼 조회문과 flush는 관련이 없는 것일까? 아니다. 아래에서 계속 알아보자.
다음으로는 findByName() 명령을 해보았다.
@Test
void test() {
final Member member1 = members.findById(1L).get();
member1.setName("eng");
final Member member2 = members.findByName("eng");
}
update 쿼리가 실행되는 것을 알 수 있다. 왜 실행 될 까? 이어서 findAll() 명령 실행 결과를 일단 보고 알아보자.
@Test
void test() {
final Member member1 = members.findById(1L).get();
member1.setName("eng");
members.findAll();
}
findAll() 명령을 하면 findName()과 마찬가지로 update 쿼리가 찍히는 것을 볼 수 있다.
그럼 이제 한 번 생각해보자.
- 애플리케이션 레벨에서 findByName()이나 findByAll() 명령한다.
- id를 통한 조회가 아니기 때문에 영속 컨텍스트의 1차 캐시를 뒤지지 않고 바로 db로 쿼리문을 찌른다.
- 찌를 때 영속 컨텍스트의 변경 내용을 동기화 하기 위해 flush한다.
위의 플로우로 플러시가 조회할 때 자동으로 이루어 진다고 유츄해 볼 수 있다.
위의 흐름대로 라면 findById(2L) 명령을 통해서도 flush가 발생해야 한다. (DB까지 가는 명령이기 때문에)
근데 flush가 발생하지 않았다!!!! 뭐지??
이유는 생각보다 간단한 이유였는데 JPA가 똑똑하기 때문이다.
DB에 조회 쿼리를 찌를 때 JPA가 무지성으로 영속성 컨텍스트의 모든 정보를 가져오는 것이 아니다.
조회 쿼리를 파악하고 이 쿼리가 영속성 컨텍스트의 쿼리들에 영향을 미칠만 하면 그때 flush를 해준다.(이것이 JPQL을 이용하면 flush가 발생한다는 내용과 일맥상통하는 것이다.)
그래서 findByName()과 findAll()은 flush가 발생하여 dirty checking이 동작해 update문이 실행되는 것이고 findById()는 update문이 실행되지 않는 것이다.
JPA를 공부할 수록 어려운 것 투성이다. 테코톡을 위해서 계속 JPA를 열심히 파봐야 겠다.