개발자로서 살아남기/JPA 이슈 및 최적화

서버개발자로서 살아남기 - JPA 사용시 조회방법에 따른 영속성 여부

코드 살인마 2024. 6. 30. 18:00
728x90

개요

스터디에서 Spring-JPA 환경에서, 쿼리메소드@Query 어노테이션을 이용한 JPQL, 네이티브 쿼리을 활용한 조회에 대해 이야기를 하다가, 3개의 조회방법 중 어떤 방법이 영속될까 라는 궁금증이 생겼다.

위에서 언급했던 3가지 조회 방법에 대해 영속성 여부을 알아보기로 한다

  • 쿼리메서드
  • Query 어노테이션을 활용한 JPQL
  • Query 어노테이션을 활용한 네이티브 쿼리

 

테스트

관련 내용을 리서치해보는 것도 좋지만, 일단 아묻따 테스트코드를 작성해서 영속성 여부를 확인하였다.

사용할 테이블은 회원 정보가 있는 테이블이고, Pk는 memberId이다.

 

세팅

@Autowired  
private MemberRepository memberRepository;  

@Autowired  
private EntityManager entityManager;

 

쿼리메서드

가장 먼저 가장 많이 사용하는 쿼리메서드를 통한 테스트이다.

@Test  
@Transactional(readOnly = true)  
void 쿼리메소드_영속성_테스트() {  
    Member byMemberIdUseQueryMethod = memberRepository.findByMemberId("ksh001");
    assertThat(entityManager.contains(byMemberIdUseQueryMethod)).isTrue();  
}

쿼리 메서드(findByMemberId)를 활용한 조회는 엔티티매니저 관리하에 있다.

참고로, @Transactional AOP가 적용되어, 해당 클래스의 프락시 객체에 entityManager 생성 코드가 데코레이션 되어 있다. 그렇기 때문에, 해당 어노테이션이 없으면 영속되지 않는다.

 

JPQL

먼저 memberRepository@Query 어노테이션을 활용하여, JPQL 쿼리를 만든다.

@Query("SELECT p FROM Member p WHERE p.memberId = :memberId")  
Member findByMemberIdUseJPQL(@Param("memberId") String memberId);
@Test  
@Transactional(readOnly = true)  
void JPQL_영속성_테스트() {  
    Member byMemberIdUseJPQL = memberRepository.findByMemberIdUseJPQL("ksh001");  
    assertThat(entityManager.contains(byMemberIdUseJPQL)).isTrue();  
}

JPQL을 사용한 조회 코드이다. 마찬가지로, 엔티티매니저 관리하에 있다.

 

Native Query

@Test  
@Transactional  
void 트랜잭션적용_네이티브쿼리메소드_테스트() {  
    Poker7Rat byMemberIdUseJPQL = poker7RatRepository.findByMemberIdUseNativeQuery("ksh001");  
    assertThat(entityManager.contains(byMemberIdUseJPQL)).isTrue();  
}

native Query을 사용한 조회 코드이다. 조회된 entity도 엔티티매니저 관리하에 있다.

결과적으로 3개 방법을 사용한 조회 모두 영속화된 entityreturn 된다.

그렇다면, 어떤 방식으로 조회하더라도, 영속화되는 걸까? 지금까지는 식별자(PK)(memberId)을 통해 조회해 봤다. 이번에는 식별자가 아닌 조건으로 조회해 보자.

 

식별자(PK)가 아닌 조건으로 조회

@Test  
@Transactional  
void 트랜잭션적용_쿼리메소드_식별자사용X_테스트() {  
    Poker7Rat byMemberIdUseJPQL = poker7RatRepository.findByMemberNo("1234567");  
    assertThat(entityManager.contains(byMemberIdUseJPQL)).isFalse();  
}

쿼리메서드로 같은 결과 값을 조회해 봤는데, 엔티티매니저 관리하에 있지 않다. 즉 영속화가 되지 않은 entityreturn 되었다.

즉 영속화 여부는 조회 방법이 아니라, 조회 조건이 식별자냐 아니냐에 따라, 달라지는 것이다.

DB에서 데이터를 가져오는데, 왜 영속될까?

1차 캐시에서 엔티티를 가져오는 건(em.find 활용) 영속되는 건 당연하다.

 

그런데, JPQL과 네이티브 쿼리는 DB에서 데이터를 가져오는데, 왜 영속되는 걸까? (물론 쿼리메서드 find 내부 코드 보면 1차 캐시에 값이 없으면 DB에서 직접 가져옴)

 

그 이유는 하이버네이트가 DB에서 데이터를 직접 가져온 후에, 엔티티와 매칭하고, 1차캐시에 집어넣기 때문이다. (이 부분에 대한 자세한 내용은 다음 포스팅 때 다룰 예정)

 

그렇다면 왜 식별자가 아닌 조건으로 조회하면, 엔티티가 영속화되지 않을까?(1차 캐시 X)

 

그 이유는 1차 캐시는 식별자를 통해 데이터를 구분하기 때문에. 식별자를 조건으로 조회해야만, 1차 캐시에 저장된다.

식별자가 아닌 조건으로 조회하더라도, return 되는 entity을 1차 캐시에 저장하면 되는 것 아닌가? 이 부분은 다음 포스팅에서 확인

 

결론

식별자로 조회하고, 트랜잭션 어노테이션을 사용한다면, 1차 캐시에 저장된다.

 

주의사항

여기서 헷갈리는 부분이 있을 수 있는데, 식별자로 조회하는 경우 1차 캐시에 모두 저장한다.커스텀 쿼리(JQPL , Native Query)로 조회하는 경우, 1차 캐시에 있는 entity을 가져오지 않고, DB에서 가져온다.

즉 쿼리메서드, 커스텀쿼리는 1차 캐시에 모두 저장하지만, 1차 캐시를 사용하는 건 쿼리메서드뿐이다.