findById vs getReferenceById 차이

2024. 4. 22. 14:41·프레임워크/자바 스프링

JPA findById vs getReferenceById

JPA(Java Persistence API)를 사용하여 데이터베이스에서 엔티티를 조회할 때 일반적으로 사용되는 두 가지 메서드가 있습니다. findById와 getReferenceById는 일부 유사점이 있지만, 중요한 차이점이 있습니다.

findById

  • 지정된 ID의 엔티티를 조회하고, 해당 엔티티가 없으면 null을 반환합니다.
  • 데이터베이스에서 엔티티를 직접 조회합니다.
  • 엔티티 객체 또는 null을 반환합니다.
  • 데이터베이스에서 엔티티를 로드하는 데 필요한 쿼리를 생성하고 실행합니다.
  • 데이터베이스 조회를 즉시 수행하고 엔티티를 직접 반환합니다.

getReferenceById

  • 지정된 ID의 엔티티에 대한 프록시 참조를 반환합니다.
  • 데이터베이스에서 엔티티를 직접 조회하지 않습니다. 대신, 엔티티에 대한 프록시 참조를 반환합니다.
  • 엔티티에 대한 프록시 참조를 반환합니다.
  • 데이터베이스에 쿼리를 실행하지 않습니다. 대신, 엔티티를 지연 로딩(lazy loading)하는 데 사용됩니다.
  • 엔티티에 실제로 접근하거나, 엔티티의 속성에 접근하거나, 엔티티의 메서드를 호출할 때 데이터베이스 조회가 발생합니다.
  • 엔티티에 대한 프록시 참조를 반환하여 지연 로딩을 구현합니다.

각 메서드의 사용 시점

findById 사용 시점

  • 데이터베이스에서 엔티티를 즉시 로드하고 싶은 경우
  • 엔티티를 직접 조작하거나, 여러 엔티티 속성에 접근하거나, 엔티티의 메서드를 호출해야 하는 경우

getReferenceById 사용 시점

  • 데이터베이스 조회를 지연시키고 싶은 경우
  • 엔티티에 대한 참조가 필요하지만, 모든 속성이나 메서드에 접근할 필요가 없는 경우
  • 성능 최적화가 필요한 경우, 불필요한 데이터베이스 접근을 줄일 수 있습니다.

코드 예시

package com.fastcampus.projectboard.service;

import com.fastcampus.projectboard.domain.Article;
import com.fastcampus.projectboard.domain.type.SearchType;
import com.fastcampus.projectboard.dto.ArticleDto;
import com.fastcampus.projectboard.dto.ArticleWithCommentsDto;
import com.fastcampus.projectboard.repository.ArticleRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@RequiredArgsConstructor
@Transactional
@Service
public class ArticleService {
    private final ArticleRepository articleRepository;


    @Transactional(readOnly = true)
    public ArticleWithCommentsDto getArticle(long articleId) {
        return articleRepository.findById(articleId)
                .map(ArticleWithCommentsDto::from)
                .orElseThrow(() -> new EntityNotFoundException("게시글이 없습니다 - articleId: " + articleId));
    }

    public void updateArticle(ArticleDto articleDto) {
        try{
            Article article = articleRepository.getReferenceById(articleDto.id());
            if (articleDto.title() != null) { article.setTitle(articleDto.title()); }
            if (articleDto.content() != null) { article.setContent(articleDto.content()); }
            article.setHashtag(articleDto.hashtag());
        } catch (EntityNotFoundException e){
            log.warn("게시글 업데이트 실패. 게시글을 찾을 수 없습니다 - dto: {}", articleDto);
        }
    }

}

코드 설명

위 코드에서 updateArticle 메서드는 getReferenceById를 사용하여 Article 엔티티를 ID로 조회합니다. 그러나 getReferenceById는 즉시 엔티티를 데이터베이스에서 로드하지 않습니다.

 

대신, 엔티티를 나타내는 프록시 객체를 반환합니다.

실제 엔티티 로딩은 엔티티의 속성이나 메서드에 액세스할 때 발생합니다. 이를 지연 로딩이라고 합니다.

 

이 경우에는 article 객체의 속성에 액세스할 때, 즉 setTitle, setContent, 또는 setHashtag 메서드를 호출할 때 실제 엔티티 로딩이 발생합니다. 이때 JPA provider(Hibernate)는 엔티티를 데이터베이스에서 조회하는 SELECT 문을 실행합니다.

 

따라서, 실제 SELECT 문이 실행되는 시점은 getReferenceById를 호출할 때가 아니라 article 객체의 속성에 액세스할 때입니다.

 

이벤트의 순서는 다음과 같습니다.

  1. getReferenceById가 호출되어 Article 엔티티를 나타내는 프록시 객체를 반환합니다.
  2. 프록시 객체가 article 변수에 할당됩니다.
  3. article 객체의 속성에 액세스할 때(예: setTitle, setContent, 또는 setHashtag), JPA 공급자는 엔티티를 데이터베이스에서 조회하는 SELECT 문을 실행합니다.
  4. 조회된 엔티티가 해당 속성을 업데이트하는 데 사용됩니다.

getReferenceById와 지연 로딩을 사용하면 엔티티 로딩을 실제로 필요할 때까지 지연할 수 있습니다. 이렇게 하면 성능을 개선할 수 있습니다.

TIP으로 한 가지 더 설명하자면, updateArticle메소드 내에서 set메소드를 통해서 article의 속성이 변경된 이후 articleRepository.save(article) 메소드를 호출하지 않은 것을 알수 있습니다.

 

즉, updateArticle 메소드는 class level transactional에 의해서 메소드 단위로 트랜잭션이 묶여 있습니다. 그래서 updateArticle메소드 호출이 끝나면 article이 변경 사항을 감지하게 됩니다. 그럼 변경된 것에 대해 query를 날리게 됩니다.
하지만 만약 명시적으로 save를 하고 싶거나 flush 메서드를 사용하고 싶다면 사용 할 수 있습니다.

 

요약하자면, findById는 데이터베이스에서 엔티티를 직접 조회하고, 해당 엔티티가 없으면 null을 반환합니다. 반면, getReferenceById는 데이터베이스 조회를 지연시키고, 엔티티에 실제로 접근하거나 속성에 접근할 때 데이터베이스 조회를 수행하는 프록시 참조를 반환합니다. 사용 시점은 성능, 지연 로딩, 직접 조작의 필요성에 따라 달라집니다.


QnA

프록시

프록시는 클라이언트와 대상 객체 사이의 중개자 역할을 하는 객체입니다. 이는 대상 객체에 대한 액세스를 제어하거나, 추가 기능을 추가하거나, 실제로 필요할 때까지 대상 객체의 생성을 지연하는 디자인 패턴입니다.

JPA의 경우, 프록시는 엔티티를 나타내는 객체지만, 실제로 데이터베이스에서 엔티티를 로드하지는 않습니다. 이렇게 하면 지연 로딩이 가능해집니다.

프록시 참조

프록시 참조는 프록시 객체에 대한 참조입니다. 이 참조를 통해 실제 엔티티에 액세스할 수 있습니다. 프록시 참조는 엔티티가 실제로 로드되지 않은 상태에서 엔티티에 대한 정보를 제공할 수 있습니다.

Lazy Loading

Lazy Loading은 실제로 필요할 때까지 데이터베이스에서 엔티티를 로드하지 않는 기법입니다. 이렇게 하면 불필요한 데이터베이스 액세스를 줄일 수 있습니다.

예를 들어, 다음과 같은 코드가 있습니다.

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    @OneToMany(mappedBy = "user")
    private List<Order> orders;
    // getters and setters
}

User user = entityManager.getReferenceById(User.class, 1L);
System.out.println(user.getName()); // 실제로 데이터베이스에서 엔티티를 로드하지 않음
List<Order> orders = user.getOrders(); // 실제로 데이터베이스에서 엔티티를 로드

위 코드에서는 getReferenceById 메서드를 사용하여 사용자 엔티티를 로드합니다. 그러나 실제로 데이터베이스에서 엔티티를 로드하지는 않습니다. 대신, 프록시 참조를 반환합니다. 그리고 getOrders 메서드를 호출할 때 실제로 데이터베이스에서 엔티티를 로드합니다.

Eager Loading

Eager Loading은 Lazy Loading의 반대 개념입니다. 즉, 실제로 필요할 때까지 기다리지 않고, 즉시 데이터베이스에서 엔티티를 로드하는 기법입니다.

예를 들어, 다음과 같은 코드가 있습니다.

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
    private List<Order> orders;
    // getters and setters
}

User user = entityManager.find(User.class, 1L);
System.out.println(user.getName()); // 실제로 데이터베이스에서 엔티티를 로드
List<Order> orders = user.getOrders(); // 이미 로드된 엔티티를 반환

위 코드에서는 find 메서드를 사용하여 사용자 엔티티를 로드합니다. 그리고 fetch = FetchType.EAGER를 사용하여 즉시 데이터베이스에서 엔티티를 로드합니다.

getReferenceById와 findById의 Use Case

getReferenceById와 findById는 다음과 같은 Use Case에서 사용됩니다.

  • getReferenceById: 엔티티에 대한 참조가 필요하지만, 실제로 엔티티를 로드하지는 않아도 되는 경우에 사용됩니다. 예를 들어, 엔티티의 존재 여부를 확인하거나, 엔티티의 일부 속성에만 액세스하는 경우에 사용됩니다.
  • findById: 엔티티를 실제로 로드해야 하는 경우에 사용됩니다. 예를 들어, 엔티티의 모든 속성에 액세스하거나, 엔티티를 조작하는 경우에 사용됩니다.

예를 들어, 다음과 같은 코드가 있습니다.

// getReferenceById 사용
User user = entityManager.getReferenceById(User.class, 1L);
if (user!= null) {
    System.out.println("User exists");
} else {
    System.out.println("User does not exist");
}

// findById 사용
User user = entityManager.find(User.class, 1L);
if (user!= null) {
    System.out.println("User name: " + user.getName());
    user.setName("New Name");
    entityManager.persist(user);
}

위 코드에서는 getReferenceById를 사용하여 사용자 엔티티의 존재 여부를 확인합니다. 그리고 findById를 사용하여 사용자 엔티티를 실제로 로드하고, 엔티티의 속성을 조작합니다.

저작자표시 (새창열림)

'프레임워크 > 자바 스프링' 카테고리의 다른 글

콘서트 예매 서비스에서 발생할 수 있는 동시성 이슈와 처리  (0) 2024.07.20
JPA 테스트 코드 작성시 UPDATE Query 생성이 안되네?  (0) 2024.05.28
JPA 연결 테스트 코드  (0) 2024.05.28
[그냥 보는] application.yaml  (0) 2024.05.28
로깅 출력 어느 것으로?(feat.Interpolation vs Concatenation)  (0) 2024.04.22
'프레임워크/자바 스프링' 카테고리의 다른 글
  • JPA 테스트 코드 작성시 UPDATE Query 생성이 안되네?
  • JPA 연결 테스트 코드
  • [그냥 보는] application.yaml
  • 로깅 출력 어느 것으로?(feat.Interpolation vs Concatenation)
hyeseong-dev
hyeseong-dev
안녕하세요. 백엔드 개발자 이혜성입니다.
  • hyeseong-dev
    어제 오늘 그리고 내일
    hyeseong-dev
  • 전체
    오늘
    어제
    • 분류 전체보기 (283)
      • 여러가지 (108)
        • 알고리즘 & 자료구조 (73)
        • 오류 (4)
        • 이것저것 (29)
        • 일기 (1)
      • 프레임워크 (39)
        • 자바 스프링 (39)
        • React Native (0)
      • 프로그래밍 언어 (39)
        • 파이썬 (31)
        • 자바 (3)
        • 스프링부트 (5)
      • 컴퓨터 구조와 운영체제 (3)
      • DB (17)
        • SQL (0)
        • Redis (17)
      • 클라우드 컴퓨팅 (2)
        • 도커 (2)
        • AWS (0)
      • 스케쥴 (65)
        • 세미나 (0)
        • 수료 (0)
        • 스터디 (24)
        • 시험 (41)
      • 트러블슈팅 (1)
      • 자격증 (0)
        • 정보처리기사 (0)
      • 재태크 (0)
        • 암호화폐 (0)
        • 기타 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Python
    Redis
    ecs
    자바
    SAA
    celery
    WebFlux
    EC2
    docker
    #개발자포트폴리오 #개발자이력서 #개발자취업 #개발자취준 #코딩테스트 #항해99 #취리코 #취업리부트코스
    FastAPI
    그리디
    Docker-compose
    시험
    Spring WebFlux
    항해99
    파이썬
    reactor
    Spring Boot
    AWS
    프로그래머스
    spring
    취업리부트
    DP
    mybatis
    java
    완전탐색
    OOP
    백준
    RDS
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
hyeseong-dev
findById vs getReferenceById 차이
상단으로

티스토리툴바