Dirty Checking의 치명적인 Weakness(단점)(feat. hint,lock)
Member member = repository.save(new Member("member1", 10));
entityManager.flush();
entityManager.clear();
Optional<Member> optional = repository.findById(member.getId()); // Context를 clear()하였으므로, DB에서 조회를 한다.
Member findMember = optional.get();
// "member1" -> "member2"로 변경(dirty checking 기법으로 변경)
findMember.setUsername("member2");
entityManager.flush();
// member2로 출력됨.
System.out.println("findMember = " + findMember);
JPA는 Dirty Checking 기법으로 setter 등으로 인해 객체의 필드값의 변경을 감지하여, 자동으로 [수정]을 해준다.
그러나 , Dirty Checking 기법에는 매우 치명적인 단점이 있다.
그건 바로, 객체 [2개]를 Context가 가지고 있어야 한다는 것이다!!
[원본] "member1"인 객체를 가지고 있어야, setter에 의해 변경된 "member2" 객체와 비교해서 변경이 되었는지를 알 수가
있다.
DB에서 조회를 하여서 Context에 들고 오는 순간, 이미 Context에는 또 다른 객체가 생성이 되버린다.
-> 이러한 JPA의 단점을 최적화할 수 있는 기능이 있다.
(JPA 표준에는 이러한 기능이 없고, [하이버네이트]가 제공하는 기능이다.)
( "나는 조회를 했지만, Setter 등으로 그 객체를 절대 변경하지는 않을 거야" 할 때, 사용하면 된다.)
(만약 조회된 객체를 Dirty Checking으로 변경할 예정이 있으면, 절대 사용하면 안된다.)
public interface MemberReposiotry extends JpaRepository<Member,Long> {
// @QueryHint[s]는 Spring Data JPA가 제공하는 에노테이션이고,
// @QueryHint(name = "asdfads...)는 [하이버네이트]가 제공하는 에노테이션이다.
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);
}
위와 같이 hint를 적용한 뒤, 아래 코드를 돌려 보자!
@Test@Transactional
public void queryHint(){
// hint 적용 후
Member member = repository.save(new Member("member1", 10));
entityManager.flush();
entityManager.clear();
// DB에서 조회한 뒤, Context에 저장될 때, 원본 객체 딱 1개만 저장이 된다.
Member findMember = repository.findReadOnlyByUsername(member.getUsername());
findMember.setUsername("member2");
entityManager.flush();
}
근데, 최적화가 된다고 해도 그 효과는 사실 실무에서는 미미하다.
대부분의 90% 이상의 경우의 트래픽 문제는 (n+1) 문제와 같이 SQL문이 많이 나가는 문제 때문에 생긴다.
그럼에도 불구하고, 아래와 같은 에노테이션을 일일이 붙이는 것은 음... 개발자의 선택에 맡기겠다.
(성능 테스트를 해보고, 이 에노테이션을 넣을 때와 뺐을 때, 차이가 의미가 있게 있으면 그때에는 최적화 하자)
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
그런데, 보통의 경우 정말 트래픽이 많은 경우, [Redis] DB를 중간에 넣어서 캐쉬용 DB를 넣어서, 성능을 최적화하는 방법
이 일반적이다.
// @Lock : Spring Data JPA에서 제공하는 에노테이션
// LockModeType.PESSIMISTIC_WRITE : JPA에서 제공하는 enum 타입입
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String username);
@Test@Transactional
public void queryLock(){
Member member = repository.save(new Member("member1", 10));
entityManager.flush();
entityManager.clear();
// 다른 쓰레드에서는 조회된 Member에 대해 접근이 불가능!!!
List<Member> findMember = repository.findLockByUsername("member1");
}
쿼리 결과
https://jbluke.tistory.com/454
SELECT ~~ FOR UPDATE(Feat. Concurrency Problem )
SELECT ~ FOR UPDATE란 SELECT ~ FOR UPDATE 구문은 "데이터 수정하려고 SELECT 하는 중이야~ 다른 사람들은 데이터에 손 대지 마!" 라고 할 수 있습니다. 좀 더 딱딱한 표현으로는 동시성 제어를 위하여 특정
jbluke.tistory.com