글을 작성하게된 계기
member Entity를 생성하고 관련 테스트 코드를 작성하던 도중에,
JPA를 상속받은 MemberRepository 에서 하나의 데이터를 조회할때 하나의 함수가 있는것이 아닌,
findById 와 getReferenceById 라는 2개의 조회함수가 있다는 것을 알게되고, 어떤 상황에서 해당 함수들을 사용하는것이 좋을까 알아보기 위하여 작성하게 되었습니다.
FindById() 와 getReferenceById() 의 Method Signature와 데이터가져오는 방식
Optional<T> findById(ID id)
- Optional<T>: 조회된 엔티티를 Optional로 감싼 반환 타입입니다. Optional은 엔티티가 존재하지 않을 수도 있는 경우에 사용됩니다.
- ID: 조회할 엔티티의 식별자(ID) 타입입니다.
- 조회된 엔티티 객체를 Optional로 감싸서 반환합니다.
- CrudRepository와 JpaRepository 인터페이스에서 모두 제공되는 메소드입니다
public T getReferenceById(ID id)
- T: 반환 타입으로 엔티티의 클래스 타입입니다.
- ID: 조회할 엔티티의 식별자(ID) 타입입니다.
- 엔티티가 영속성 컨텍스트에 없으면 EntityNotFoundException이 발생할 수 있음
- JpaRepository 인터페이스에서만 제공됨
- 데이터가져오는 방식
- 지연로딩(Lazy Loading) : 엔티티를 실제로 사용할 때까지 데이터베이스 조회를 지연합니다
- 실제 엔티티 객체가 필요한 시점에서는 프록시 객체가 아닌 실제 엔티티를 반환합니다. 처음에는 Proxy객체로 가지고 있습니다.
위 코드를 보면, 우선 getOne같은경우 단순히 T (Entity Type)만을 반환해주고, FindById()는 Optional<T> (Optional <Entity>) 타입으로 존재하는지 안하는지를 Optional 하게 제공해주고 있습니다.
테스트코드
findById()
@Test
public void testSelect(){
Long member_no = 13L;
Optional<Member> result = memberRepository.findById(member_no);
System.out.println("=============================");
if(result.isPresent()){
Member member = result.get();
System.out.println(member);
}
}
결과값
select
m1_0.member_no,
m1_0.member_from_social,
m1_0.member_id,
m1_0.member_nickname,
m1_0.member_password
from
member m1_0
where
m1_0.member_no=?
=============================
Member(member_no=5, member_id=user5, member_password=123123123, member_nickname=사용자5, member_from_social=false)
getReferenceById()
@Test
@Transactional
public void testSelect(){
Long member_no = 5L;
Member result_get = memberRepository.getReferenceById(member_no);
System.out.println("=============================");
System.out.println(result_get);
}
결과값
=============================
Hibernate:
select
m1_0.member_no,
m1_0.member_from_social,
m1_0.member_id,
m1_0.member_nickname,
m1_0.member_password
from
member m1_0
where
m1_0.member_no=?
Member(member_no=5, member_id=user5, member_password=123123123, member_nickname=사용자5, member_from_social=false)
결과 차이점
FindById()
=============================
위의 "==========================" 를 보면,
FindById()의 경우 "===================="가 findByID가 즉시 실행되고서 출력됩니다.
즉, findById가 먼저 실행됩니다.
getReferenceByID()
getReferenceByID() 의 경우
=============================
System.out.println(result_get);
를 호출하니, member가 필요하다는 것을 알고서 proxy 객체가 그제서야 Select문을 실행시키고 있습니다.
여기서 getReferenceById()의 특징이 완전하게 설명되지는 않았습니다.
만약, getReferenceById를 통해서 ID값을 가져오면 어떻게 될까요?
@Test
@Transactional
public void testSelect(){
Long member_no = 5L;
Member result_get = memberRepository.getReferenceById(member_no);
System.out.println("=============================");
System.out.println(result_get.getMember_no());
}
결과값
SQL이 실행되지 않았습니다.
=============================
5
이처럼, getReferenceById를 통해서 ID값만을 가져오고 해당 객체의 ID값만 접근하게 된다면, 해당 Proxy가 참조값을 가지고 있을 수 있습니다. 만약 해당 proxy가 존재하지 않는다면, EntityNotFoundException 이 발생하게 됩니다.
결론, 그래서 언제 어디서 사용하는것이 좋을까???
위의 차이점을 보면 알수 있듯이,
FindById() 는 무조건 해당 SQL을 곧바로 실행하고,
getReferenceById()는 만약에 함수를 호출하고서, 실제로 그 데이터를 출력하거나 사용할때 SQL이 실행됩니다. (다만, ID값을 호출할경우 SQL이 필요하지 않습니다.)
일반적인 상황에서는 FindById()를 통해 Optional 한 값을 활용하여 예외처리를 진행하는것이 좋아보입니다.
단순히, Entity 의 ID값만 필요한 상황이라면, getReferenceById()를 통해 Select문이 실행되는 처리를 제외할 수 있으므로 getReferenceById()를 통해 처리하는것도 고려하는것이 좋을 것 같습니다. 이러한 상황에서는, EntityNotFoundException을 피하기 위해 반드시 DB에 해당 데이터가 있는 상황이라는 것도 고려할 필요가 있습니다.
도움받은 글
스택오버플로우, https://stackoverflow.com/questions/24482117/when-use-getone-and-findone-methods-spring-data-jpa
스프링getReferenceById Method Signature, https://docs.spring.io/spring-data/data-jpa/docs/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html#getReferenceById(ID)
findById, getById 블로그, https://k3068.tistory.com/103
[JPA] 연관 관계를 가진 엔티티 저장 방식 개선 (불필요한 select문 제거)