본문 바로가기

Development/Spring

[Spring] Spring Data JPA, Repository

Spring Data JPA

  • Spring Data JPA는 JpaRepository를 기반으로 더욱 쉽게 데이터베이스를 사용할 수 있는 아키텍처를 제공
  • Springboot로 JpaRepository를 상속하는 인터페이스를 생성하면, 기존의 다양한 메소드를 손쉽게 활용할 수 있음

Repository

  • Spring Data JPA가 제공하는 인터페이스
  • 엔티티가 데이터베이스의 테이블과 구조를 생성하는 데 사용했다면, 리포지토리(Repository)는 엔티티가 생성한 데이터베이스에 접근하는 용도로 사용
  • 리포지토리를 생성하려면 테이블과 엔티티에 대한 인터페이스를 생성하고, 아래 코드와 같이 JpaRepository를 상속받으면 됨
package org.spring.study.data.repository;

import org.spring.study.data.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {

}
  • JpaRepository를 상속받을 때는 대상 엔티티 클래스와 그 엔티티의 Id 타입을 설정해야함.
  • JpaRepository는 아래와 같은 기본 메소드를 제공함
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    void flush();

    <S extends T> S saveAndFlush(S entity);

    <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

    /** @deprecated */
    @Deprecated
    default void deleteInBatch(Iterable<T> entities) {
        this.deleteAllInBatch(entities);
    }

    void deleteAllInBatch(Iterable<T> entities);

    void deleteAllByIdInBatch(Iterable<ID> ids);

    void deleteAllInBatch();

    /** @deprecated */
    @Deprecated
    T getOne(ID id);

    /** @deprecated */
    @Deprecated
    T getById(ID id);

    T getReferenceById(ID id);

    <S extends T> List<S> findAll(Example<S> example);

    <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

@Repository 어노테이션

@Repository 어노테이션을 적용하면, 그 클래스는 Bean 객체화 된다. 따라서 Bean의 생명주기를 따르게 되고, @Autowired와 같은 어노테이션으로 의존성을 주입할 수 있다. 또한 @Component 어노테이션을 상속하고 있으며, Spring이 자동으로 해당 클래스를 발견하고 Bean에 등록한다. 결과적으로 @Repository가 달려있는 클래스는  Spring이 관리하는 Bean이 되며, 데이터에 접근하는 계층으로 인식된다.

JpaRepository의 상속 구조

이미지 출처:&nbsp;https://i.stack.imgur.com/ee7XF.jpg

이러한 리포지토리 추상화는 아키텍처와 기능적 요구에 따라 기본 리포지토리를 선택할 수 있게 함.

CrudRepository

  •  기본적인 CRUD(Create, Read, Update, Delete) 작업을 수행하는 메서드를 제공
  • 모든 JPA 엔티티에 적용 가능
  • 엔티티 생성, 조회, 수정, 삭제와 같은 간단한 데이터베이스 작업

PagingAndSortingRepository

  • CrudRepository의 기능을 포함하며, 페이징(데이터 분할) 및 정렬 기능을 추가로 제공
  • 페이지 단위로 데이터를 조회하고, 정렬 조건을 설정할 수 있음

JpaRepository

  • CrudRepository 및 PagingAndSortingRepository의 기능을 포함하며, JPA 특화 기능을 추가로 제공
  • JPA 엔터티에 대한 JPQL(Java Persistence Query Language) 쿼리를 작성할 수 있음
  • 복잡한 데이터 쿼리 가능
  • JpaRepository의 deleteInBatch(…)는 delete(…)와는 다르게 지정된 엔티티를 삭제하는 쿼리를 사용하므로 더 성능이 좋지만 JPA 정의된 cascade를 트리거하지 않는다는 부작용이 있음.

QueryByExampleExecuter

  • 예시 기반 쿼리를 사용하여 엔티티를 검색하는 기능을 제공
  • 엔티티 객체의 예시를 사용하여 동일한 속성을 가진 엔티티를 검색할 수 있음
  • 동적 쿼리
  • 엔티티 객체 기반 검색

Repository의 목적과, 주의해야할 점

기본적으로 리포지토리를 선택하는 것은 두 가지 주요 목적이 있음

  1. Spring Data repository 인프라가 사용자의 인터페이스를 찾아 프록시 생성을 트리거하게 하고, 클라이언트로 인터페이스 인스턴스를 주입할 수 있게 함
  2. 필요한 기능을 가능한 한 많이 인터페이스에 포함시키기 위해 추가 메소드 선언 없이 사용

기본 인터페이스에 의존하는 것의 단점은 

  1. Spring Data repository 인터페이스에 의존하면, 리포지토리 인터페이스가 라이브러리에 종속됨
  2. 예를 들어 CrudRepository를 확장함으로써, 한 번에 모든 지속성 메소드가 노출됨.
    이것은 더 세밀한 제어를 원하는 상황에서는 문제가 될 수 있다.

이러한 단점들을 해결하는 방법은 사용자가 자신의 기본 리포지토리 인터페이스를 만드는 것.

interface ApplicationRepository<T> extends PagingAndSortingRepository<T, Long> { }

interface ReadOnlyRepository<T> extends Repository<T, Long> {

  // Al finder methods go here
}

첫 번째 리포지토리 인터페이스는 일반적인 목적의 기본 인터페이스로, 실제로는 1번 포인트만을 고정하지만, 일관성을 위해 ID 타입을 Long으로 연결함

두 번째 인터페이스는 보통 CrudRepository와 PagingAndSortingRepository에서 복사된 모든 find…(…) 메소드를 가지고 있지만, 조작 메소드는 노출하지 않는다. 

Repository 메서드 생성 규칙

리포지토리에서 제공하는 조회 메서드는 기본값으로 단일 조회하거나 전체 엔티티 조회하는 것만 지원하고 있어, 필요에 따라 다른 조회 메서드가 필요함

메서드에 이름을 붙일 때는 첫 단어를 제외한 이우 단어들의 첫 글자를 대문자로 설정해야 JPA에서 정상적으로 인식하고 쿼리를 자동으로 만들어줌.

다음은 몇 가지 주요 키워드와 사용 예이다:

  1. findBy - 특정 필드를 기준으로 검색한다. 예: `findByName(String name)`은 `name` 필드가 주어진 값과 일치하는 엔티티를 반환한다.
  2. And - 여러 필드를 기준으로 검색한다. 예: `findByNameAndAge(String name, Integer age)`는 `name`과 `age` 필드가 각각 주어진 값과 일치하는 엔티티를 반환한다.
  3. Or - 한 개 이상의 필드를 기준으로 검색한다. 예: `findByNameOrAge(String name, Integer age)`는 `name` 또는 `age` 필드가 주어진 값과 일치하는 엔티티를 반환한다.
  4. Is, Equals - 특정 필드가 주어진 값과 일치하는지 검사한다. 예: `findByNameIs(String name)` 혹은 `findByNameEquals(String name)`은 `name` 필드가 주어진 값과 일치하는 엔티티를 반환한다.
  5. Between - 특정 필드의 값이 두 값 사이에 있는 엔티티를 검색한다. 예: `findByAgeBetween(int start, int end)`는 `age` 값이 `start`와 `end` 사이에 있는 엔티티를 반환한다.
  6. LessThan, GreaterThan, LessThanEqual, GreaterThanEqual - 특정 필드의 값이 주어진 값보다 작거나 큰 엔티티를 검색한다. 예: `findByAgeLessThan(int age)`는 `age` 필드의 값이 주어진 값보다 작은 엔티티를 반환한다.
  7. IsNull, IsNotNull - 특정 필드의 값이 null인지 아닌지를 기준으로 검색한다. 예: `findByNameIsNull()`은 `name` 필드의 값이 null인 엔티티를 반환한다.
  8. Like, NotLike - 특정 필드의 값이 주어진 패턴과 일치하는지를 기준으로 검색한다. 예: `findByNameLike(String pattern)`은 `name` 필드의 값이 주어진 패턴과 일치하는 엔티티를 반환한다.
  9. In, NotIn - 특정 필드의 값이 주어진 컬렉션에 포함되어 있는지를 기준으로 검색한다. 예: `findByNameIn(Collection<String> names)`은 `name` 필드의 값이 주어진 컬렉션에 포함된 엔티티를 반환한다.

이 외에도 `OrderBy`, `Top`, `First` 등 다양한 키워드를 사용할 수 있다. 이 방식은 복잡한 쿼리를 작성하지 않고도 간단한 검색을 수행할 수 있어 편리하다.

참고 자료

 

What is difference between CrudRepository and JpaRepository interfaces in Spring Data JPA?

What is the difference between CrudRepository and JpaRepository interfaces in Spring Data JPA? When I see the examples on the web, I see them there used kind of interchangeably. What is the differ...

stackoverflow.com