2024. 12. 17. 11:10ㆍSpring/Spring Docs
- 핵심 개념
- 메서드 질의하기
- Repository Interface 정의하기
1. 핵심 개념
Spring Data Repository 추상화의 목표는 다양한 퍼시스턴스 저장소의 데이터 접근 계층을 구현하는데에 필요한 보엘러플레이트 코드를 줄이는 것입니다.
Spring Data Repository 추상화의 중심 인터페이스는 Repository 입니다.
이것은 도메인 클래스를 관리하면서 도메인클래스의 식별자를 유형인수로써 사용합니다.
Spring Data에서는 도메인 타입을 Entity 라고 부릅니다. (= 또는 Aggregate 라고도 부릅니다.)
각 도메인 객체는 식별자(ID) 가 있고, 데이터를 액세스할때에 이 값을 활용합니다.
Spring Data Commons에서 제공하는 Repository의 확장 인터페이스로는 CrudRepository, ListCrudRepository, PagingAndSortingRepository, ListPagingAndSortingRepository 가 있습니다.
이 확장 인터페이스를 실질적으로 사용하고자하는 저장소 모듈(JPA, mongoDB 등...)에서 지원되는지 사전에 확인한 후, 지원되면 사용하면 됩니다.
1.1. 확장 인터페이스
CrudRepository와 ListCrudRepository는 Repository인터페이스의 상속 인터페이스로, 엔티티 클래스에 대한 정교한 CURD 기능을 제공해줍니다.
ListCrudRepository는 CrudRepository에서 Iterable 객체로 반환하는 값을 List형태로 반환하는 것만 차이가 있습니다.
@NoRepositoryBean public interface CrudRepository<T, ID> extends Repository<T, ID> { <S extends T> S save(S entity); <S extends T> Iterable<S> saveAll(Iterable<S> entities); Optional<T> findById(ID id); boolean existsById(ID id); Iterable<T> findAll(); Iterable<T> findAllById(Iterable<ID> ids); long count(); void deleteById(ID id); void delete(T entity); void deleteAllById(Iterable<? extends ID> ids); void deleteAll(Iterable<? extends T> entities); void deleteAll(); }
@NoRepositoryBean public interface ListCrudRepository<T, ID> extends CrudRepository<T, ID> { <S extends T> List<S> saveAll(Iterable<S> entities); List<T> findAll(); List<T> findAllById(Iterable<ID> ids); }
Repository는 기본적으로 프로퍼티명을 기반한 Query Methods 를 제공하지만, PK의 경우 PK로 지정한 프로퍼티명과 무관하게 Id라는 명칭으로 쿼리메서드를 제공합니다. (ex. findById
)
프로퍼티명에 기반한 Query Methods 를 사용하는 것 외에 직접 쿼리를 사용하고 싶다면 @Query 애너테이션을 사용하여 사용자 지정 쿼리를 사용할 수 있습니다.
엔티티에 대한 페이징된 액세스를 편리하게 해주는 메서드를 포함한 PagingAndSortingRepository 도 있습니다.
목록을 Iterable 객체가 아닌 List객체로 반환하는 ListPagingAndSortingRepository 확장 인터페이스가 있습니다.
@NoRepositoryBean public interface PagingAndSortingRepository<T, ID> extends Repository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); }
1.2. 기타 파생 쿼리
엔티티를 조회하는 것 외에도 count나 존재여부를 조회하는 파생쿼리도 사용할 수 있고,
interface UserRepository extends CrudRepository<User, Long> { long countByLastname(String lastname); boolean existsByLastname(String lastname); }
삭제 쿼리도 아래와 같이 사용할 수 있습니다.
interface UserRepository extends CrudRepository<User, Long> { long deleteByLastname(String lastname); List<User> removeByLastname(String lastname); }
2. 메서드 질의하기
Spring Data를 사용하여 애플리케이션에서 사용하고자하는 저장소에 대한 쿼리를 선언하고 사용하는 방법은 4가지 단계를 거칩니다.
- Repository 또는 Repository 의 하위 인터페이스를 상속한 인터페이스를 선언하고 대상이되는 도메인클래스와 그 도메인 클래스의 PK타입을 설정
- 그 인터페이스 내에 사용하고자하는 쿼리 메서드를 선언
- JavaConfig (또는 XML config)에 Repository 가 위치한 패키지를 지정
- 서비스 클래스에서 해당 Repository를 주입하여 사용
2.1. Repository 인터페이스 정의
Repository 또는 Repository의 하위 인터페이스를 상속하는 인터페이스를 정의합니다.
PK 타입이 Long인 User 라는 엔티티 도메인이 있다고 할 때,
이 도메인 엔티티를 처리할 Repository 인터페이스를 정의해봅시다.
@Getter @ToString @Entity public class User { @Id @GeneratedValue(strategy = jakarta.persistence.GenerationType.IDENTITY) private final Long id; private String username; private String email; protected User() { this.id = null; } public User(Long id, String username, String email) { this.id = id; this.username = username; this.email = email; } User withId(Long id) { return new User(id, this.username, this.email); } }
CrudRepository에 처리하고자하는 도메인 엔티티 타입과 그 도메인의 PK인 Long 타입을 설정합니다.
package me.jiniworld.sdc.store.jpa.user; interface UserRepository extends CrudRepository<User, Long> { }
2.2. 쿼리 메서드 선언
생성한 Repository 인터페이스 내에 쿼리 메서드를 선언합니다.
아래 코드는 Spring Data Commons에서 기본적으로 제공해주는 CrudRepository를 사용한 예제 코드입니다.
사용하고자하는 저장소와 프레임워크에 따라 ReactiveCrudRepository, CoroutineCrudRepository 등 다양한 Repository를 활용할 수 있습니다.
package me.jiniworld.sdc.store.jpa.user; import org.springframework.data.repository.CrudRepository; interface UserRepository extends CrudRepository<User, Long> { boolean existsByEmail(String email); }
2.3. Java 구성파일 설정
사용하고자하는 Spring Data store 타입이 단 하나일 경우에는 구성파일 설정을 생략해도 기능이 동작하지만,
JPA, MongoDB 등 여러개의 Spring Data 프로젝트를 하나의 애플리케이션 내에 정의하는 경우에는 반드시 config 설정을 추가해야 합니다.
아래 예시는 JPA를 사용하고 있는 코드에 대한 예제입니다.
JPA를 활용한 Repository 가 위치할 패키지를 정의하여, 설정한 패키지 내에 위치한 인터페이스에 대해서 Proxy 객체를 생성하도록 합니다.
@EnableJpaRepositories(basePackages = {"me.jiniworld.sdc.store.jpa"}) @Configuration public class JpaConfig { }
2.4. Repository 주입
위에서 작성했던 Repository 의 객체를 주입합니다.
UserRepository에 @Component 나 @Repository 와 같은 애너테이션을 설정하지 않아도 @EnableJpaRepositories로 인터페이스를 모두 등록했기에 객체를 주입할 수 있습니다.
@Transactional(readOnly = true) @RequiredArgsConstructor @Service class UserServiceImpl implements UserService { private final UserRepository userRepository; @Override public User findById(Long id) { return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found")); } @Override @Transactional public User save(User user) { return userRepository.save(user); } }
3. Repository Interface 정의하기
CrudRepository나 JpaRepository 코드를 살펴보면 @NoRepositoryBean 라는 애너테이션이 붙은 것을 확인할 수 있습니다.
@NoRepositoryBean public interface CrudRepository<T, ID> extends Repository<T, ID> { ... }
Repository 를 확장한 다른 인터페이스에도 @NoRepositoryBean 애너테이션이 설정된것을 확인할 수 있는데, 이 애너테이션은 특정 도메인 엔티티와 연결된 것이 아닌 공통 쿼리메서드를 정의할 인터페이스를 선언하는 애너테이션 입니다.
참고로 @NoRepositoryBean 애너테이션을 설정할 경우, Spring Data가 런타임 중에 해당 인스턴스를 생성하지 않게 해줍니다.
만약 여러 도메인 엔티티에서 공통적으로 활용될 쿼리 메서드들이 있다면 커스텀 베이스 Repoistory를 선언하는 것도 좋습니다.
@NoRepositoryBean interface MyBaseRepository<T, ID> extends Repository<T, ID> { Optional<T> findById(ID id); <S extends T> S save(S entity); } interface UserRepository extends MyBaseRepository<User, Long> { User findByEmail(String email); }
++
- Working with Spring Data Repositories
- Defining Repository Interfaces
'Spring > Spring Docs' 카테고리의 다른 글
[Spring Data Commons] 1. 개요 (0) | 2024.12.16 |
---|---|
[Spring Boot Core] 2.2. Externalized Configuration (2) | 2024.12.09 |
[Spring Boot Core] 2.1. Externalized Configuration 개요와 JSON Application Properties 설정하기 (0) | 2024.11.03 |
[Spring Boot Core] 1. SpringApplication (0) | 2024.10.22 |
[Spring Boot Core] Actuator를 이용한 환경 변수 모아보기 (0) | 2024.10.15 |