작성
·
221
2
밑에 질문을 보면 findByusername으로 member을 찾아올 때 1차캐시에서 가져오는 것이 아닌 DB를 통해 가져온다고 하셨는데 그러면 em.clear를 해주지 않아도 member5는 db에서 조회해서 가져오기 때문에 age가 41로 되야하는 것 아닌가요??? 이 부분이 잘이해가 안됩니다 ㅠㅠ
답변 1
6
안녕하세요. syh님
핵심은 JPA는 영속성 컨텍스트의 동일성을 보장하기 때문입니다.
좀 더 풀어서 설명드리면
CASE1 엔티티를 먼저 조회해둔 상황
1. 엔티티를 조회함
엔티티 상태:(id=1, age=40) | DB 상태(id=1, age=40)
2. 벌크 연산으로 +1
엔티티 상태:(id=1, age=40) | DB 상태(id=1, age=41)
3. findByUsername 조회
selelct m from Member; 로 조회했지만, 결과 id=1 값이 이미 영속성 컨텍스트에 있으므로 DB에서 조회한 값을 버리고 영속성 컨텍스트에서 조회한 값을 반환함
결과적으로 조회된 최종 데이터는 (id=1, age=40)
CASE2 엔티티를 먼저 조회하지 않은 상황
1. 영속성 컨텍스트가 관련하는 엔티티가 없음
엔티티 없음 | DB 상태(id=1, age=40)
2. 벌크 연산으로 +1
엔티티 없음 | DB 상태(id=1, age=41)
3. findByUsername 조회
selelct m from Member; 로 조회했고, 결과 id=1 값이 영속성 컨텍스트에 없으므로, DB에서 조회한 값을 영속성 컨텍스트에서 관리
엔티티 상태:(id=1, age=41) | DB 상태(id=1, age=41)
그리고 답변했던 내용을 다시 가져왔습니다. 참고부탁드려요.
=================
findByUsername 메서드는, 스프링 데이터 JPA가 제공하는 메서드 이름으로 쿼리 호출 기능을 사용하신 것 같네요.
이 경우 스프링 데이터 JPA는 메서드 이름으로 JPQL을 만들어서 실행합니다.
결국 "select m from member m where m.username = ?" 같은 JPQL이 실행된 것이지요.
em.find()나, 지연로딩을 조회할 때는 1차 캐시에서 엔티티를 찾아오는 과정을 거칩니다.
반면에 JPA에서 JPQL은 항상 SQL로 번역되어서 DB를 통해 실행됩니다!
왜냐하면 em.find() 처럼 엔티티 하나를 찾아오는 것은 JPA 구현체 입장에서 key(식별자) 값이 명확하기 때문에 1차 캐시에서 찾기가 간단합니다. 그런데 JPQL은 식별자를 딱 찍어서 찾는 것도 아니고, 쿼리에 따라 1차 캐시보다 더 많은 데이터가 DB에 있을 수 도 있습니다. (예를 들어서 위 코드에서 벌크 연산 실행 직후에 누군가 member5의 데이터를 DB에 더 입력한다면 1차 캐시의 데이터 만으로는 그것을 다 알 수 없지요.) 그리고 기술적으로 JPQL을 분석해서 1차 캐시에서 조회하는 하도록 만드는 것도 매우 어렵습니다.
그래서 하이버네이트 구현체는 우선 JPQL을 실행하면 DB에서 데이터를 조회합니다.
단! 여기서 부터가 매우 중요한데요. (어드벤스 입니다 ㅎㅎ)
현재 1차 캐시에 다음과 같은 데이터가 있고,
ID: 1, name: memberA
DB에 다음과 같은 데이터가 있을 때
ID: 1, name: memberB
예를 들어서 다음과 같은 JPQL을 실행하면
select m from member m where m.id = 1
우선 JPQL이기 때문에 DB에서 쿼리로 id:1, memberB 데이터를 조회합니다.
그런데 1차 캐시에 이미 id:1 이라고, 식별자가 충돌이 됩니다.
JPA는 영속성 컨텍스트의 동일성을 보장합니다.
따라서 DB의 결과 값을 버리고, 1차 캐시에 있는 결과값을 반환합니다.
이 부분에 대해서 더 자세한 내용은 JPA책 10.6.2 영속성 컨텍스트와 JPQL을 참고해주세요^^
=================
감사합니다.