본문 바로가기
Java | spring/JPA

JPA 조회 동작 방식 비교 : findById(), getOne(), getReferenceById()

by 워니 wony 2024. 3. 27.

 

 

JPA 주요 조회 메서드는 아래와 같다.

  • findById()
  • getReferenceById()
  • getOne() → 현재는 Deprecated된 메서드

아무 생각없이 쓰던 findById()와 다른 메서드는 어떤 차이를 가지는지 동작 방식과 구현 코드를 보며 비교해 보자.

 

JpaRepository

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}
  • JPA를 사용하면 엔티티 단위로 위와 같이 JpaRepository를 상속 받은 Repository interface를 생성
  • 위와 같이 JpaRepository 상속 받는 경우 기존에 구현된 기능을 그대로 사용할 수 있음
    • 사용 가능 주요 메서드
      • <S extends T> S save(S entity)
      • <S extends T> Iterable<S> saveAll(Iterable<S> entities)
      • Optional<T> findById(ID id)
      • Iterable<T> findAll();
      • getOne(ID id)
      • getReferenceById(ID id);
      • void deleteById(ID id)
      • void deleteAll()

 

JpaRepository diagram

  • JpaRepository를 상속 받으면 다양한 메서드 사용 가능
  • 위 다이어그램에서 볼 수 있듯이 JpaRepository는 다양한 인터페이스를 상속함
@NoRepositoryBean
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
	...
}
  • ListCrudRepository<T, ID>
    • CRUD(Create, Read, Update, Delete) 작업 지원하는 Repository 인터페이스
    • Spring Data JPA를 사용하여 엔티티의 데이터를 생성, 조회, 수정, 삭제하는 메서드 제공
    • 주요 메서드 : save, findById, findAll, deleteById 
  • ListPagingAndSortingRepository<T, ID>
    • 페이징과 정렬 기능을 추가한 인터페이스
    • 주요 메서드 : findAll(Pageable pageable), findAll(Sort sort) 
  • QueryByExampleExecutor<T>
    • Spring Data JPA에서 제공하는 인터페이스로, Query by Example(QBE) 기능 지원
    • 동적인 쿼리를 생성하는 방식으로, 동적인 검색 조건을 쉽게 지정 가능
    • 주요 메서드 : findAll(Example<T> example) 

 

getOne()

  • Optional<T> 가 아닌 T를 반환하는 메서드
  • entity 리턴하고 없는 경우 예외를 발생 시킴
@Test
void test() {
	log.info("=== book.getOne() start ===");
	Book book = bookRepository.getOne(1L);
	log.info("=== book.getOne() end ===");
	log.info("book.getClass() : {}", book.getClass());
	log.info("book 데이터 접근");
	log.info("book.toString : {}", book);
}

  • getOne() 호출 시 바로 데이터를 DB에 조회하는게 아니라 lazy-loading으로 프록시 객체 세팅
  • getOne() 호출 하는 시점이 아니라 데이터에 접근하는 시점에 select 조회 쿼리 발생
  • 만약 데이터 접근 시점에 해당 정보가 없는 경우 아래와 같은 예외 발생
    • jakarta.persistence.EntityNotFoundException

 

  • JPA 구현 코드
    • 내부적으로 getReferencById() 호출
    • @Deprecated 되어 이제는 getOne()이 아닌 getReference() 사용해야 함
      • Spring Data JPA 2.5.3 이후 부터 Deprecated됨
  • @Repository
    @Transactional(readOnly = true)
    public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
    
    	@Deprecated
    	@Override
    	public T getOne(ID id) {
    		return getReferenceById(id);
    	}
    		
    	@Override
    	public T getReferenceById(ID id) {
    	
    		Assert.notNull(id, ID_MUST_NOT_BE_NULL);
    		return entityManager.getReference(getDomainClass(), id);
    	}
    
    	...
    }
    

참고

  • getOne() 의 경우 사용하면 인텔리제이에서 deprecated 라고 노란줄로 표시 됨
    • 이제는 더 이상 사용되지 않고 getReferenceById() 사용하라고 추천해 줌

 

getReferenceById()

  • getReferenceById() 메서드는 getOne() 메서드와 동일한 기능
  • 이 메서드는 엔티티의 ID를 매개변수로 받아 해당 엔티티를 반환.
  • 데이터가 존재하지 않는 경우에는 EntityNotFoundException 예외 발생 시킴
  • getOne() 메서드와 마찬가지로 데이터에 접근하는 시점에 데이터베이스에서 조회 쿼리 실행 됨
@Test
void test() {
  log.info("=== book.getReferenceById() start ===");
  Book book = bookRepository.getReferenceById(1L);
  log.info("=== book.getReferenceById() end ===");
  log.info("book.getClass() : {}", book.getClass());
  log.info("book 데이터 접근");
  log.info("book.toString : {}", book);
}

  • getReferenceById() 호출 시 프록시 객체를 반환하고 실제 데이터 사용 시점에 DB에 조회 쿼리 실행
  • 만약 동일 id로 같은 트랜잭션 안에서도 조회를 했었다면 영속성 컨텍스트에 엔티티 로딩되어 있어 DB 조회 쿼리 나가지 않고 영속성 컨텍스트의 엔티티 반환

  • 반환하는 객체도 프록시가 아닌 엔티티 객체!
    • book.getClass() : class com.won.bookdomain.domain.Book

 

  • JPA 내부 구현
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

	...

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.JpaRepository#getReferenceById(java.io.Serializable)
	 */
	@Override
	public T getReferenceById(ID id) {

		Assert.notNull(id, ID_MUST_NOT_BE_NULL);
		return entityManager.getReference(getDomainClass(), id);
	}
}
public class SessionImpl
		extends AbstractSharedSessionContract
		implements Serializable, SharedSessionContractImplementor, JdbcSessionOwner, SessionImplementor, EventSource,
				TransactionCoordinatorBuilder.Options, WrapperOptions, LoadAccessContext {
	
	@Override
	public Object getReference(String entityName, Object id) {
		checkOpen();

		try {
			return byId( entityName ).getReference( id );
		}
		catch ( MappingException | TypeMismatchException | ClassCastException e ) {
			throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) );
		}
		catch ( RuntimeException e ) {
			throw getExceptionConverter().convert( e );
		}
	}
	...
}

 

findById()

  • findById() 는 엔티티의 ID를 매개변수로 받아 Optional<T> 반환
  • 데이터가 존재하지 않을 경우 Optional.empty() 반환하여, NoSuchElementException발생 하지 않음
  • findById() 메서드는 호출 시점에 데이터베이스에서 조회 쿼리 실행
@Test
void test() {
    log.info("=== book.findById() start ===");
    Optional<Book> optionalBook = bookRepository.findById(1L);
    log.info("=== book.findById() end ===");
    log.info("book 데이터 접근");
    Book book = optionalBook.orElseThrow();
    log.info("book.getClass() : {}", book.getClass());
    log.info("book.toString : {}", book);
}

 

  • JPA 구현 코드
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {

	...
	
	/**
	 * Retrieves an entity by its id.
	 *
	 * @param id must not be {@literal null}.
	 * @return the entity with the given id or {@literal Optional#empty()} if none found.
	 * @throws IllegalArgumentException if {@literal id} is {@literal null}.
	 */
	Optional<T> findById(ID id);
	
}
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

	...

	@Override
	public Optional<T> findById(ID id) {

		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		Class<T> domainType = getDomainClass();

		if (metadata == null) {
			return Optional.ofNullable(entityManager.find(domainType, id));
		}

		LockModeType type = metadata.getLockModeType();
		Map<String, Object> hints = getHints();

		return Optional.ofNullable(type == null ? entityManager.find(domainType, id, hints) : entityManager.find(domainType, id, type, hints));
	}
	
}
반응형

댓글