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));
}
}
반응형
'Java | spring > JPA' 카테고리의 다른 글
JPA 영속성 컨텍스트 장점(+ 테스트 코드) (0) | 2023.10.09 |
---|---|
JPA 영속성 컨텍스트 및 내부 동작 방식 (2) | 2023.10.08 |
JPA 사용 하는 이유는? (+ ORM 과 SQL mapper 비교) (0) | 2022.10.17 |
JPA Entity & 영속성 컨텍스트 & 라이프 사이클 알아보자! (0) | 2022.10.14 |
JPA란 무엇인가? Mybatis와 차이점 (+기초 무료 인강 추천) (0) | 2022.10.14 |
댓글