Spring Data JPA의 Repository 시스템 이해하기
Spring Data JPA를 사용하면 기본적인 CRUD 기능을 손쉽게 제공받을 수 있습니다.
예를 들어, JpaRepository를 상속받으면 findById(), save(), delete() 등의 메서드를 자동으로 사용할 수 있죠.
public interface PostRepository extends JpaRepository<Post, Long> {
}
위 코드만으로도 Post 엔티티에 대한 기본적인 데이터 접근 기능을 사용할 수 있습니다.
하지만, 복잡한 쿼리나 커스텀 로직이 필요할 때는 어떻게 해야 할까요?
이럴 때 필요한 것이 바로 커스텀 Repository 구현체입니다.
Spring Data JPA의 커스텀 Repository 적용 방법
Spring Data JPA는 커스텀 Repository 인터페이스와 구현체를 자동으로 매핑하여 사용할 수 있도록 지원합니다.
커스텀 Repository를 구현하는 과정은 다음과 같습니다.
1️⃣ 커스텀 Repository 인터페이스 정의
public interface SearchPostRepository {
Page<Post> findPostsBySearchOptionsToPage(Pageable pageable, PostSearchOptions postSearchOptions);
}
📌 PostRepositoryCustom은 기존 PostRepository에서 제공하지 않는 커스텀 메서드를 정의하는 인터페이스입니다.
2️⃣ 커스텀 Repository 구현체 작성
@RequiredArgsConstructor
public class SearchPostRepositoryImpl implements SearchPostRepository {
private final JPAQueryFactory queryFactory; // QueryDSL 사용
@Override
public Page<Post> findPostsBySearchOptionsToPage(Pageable pageable, PostSearchOptions searchOptions) {
List<Post> content = queryFactory
.selectFrom(post)
.leftJoin(post.user, user).fetchJoin()
.where(
searchKeywordContains(searchOptions.getSearchType(), searchOptions.getSearchKeyword()),
createdDateBetween(searchOptions.getStartDate(), searchOptions.getEndDate()),
viewCountsGoe(searchOptions.getMinViewCounts()),
commentCountsGoe(searchOptions.getMinCommentCounts()),
likesGoe(searchOptions.getMinLikes())
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(getOrderSpecifier(searchOptions.getSortBy(), searchOptions.getSortDirection()))
.fetch();
Long count = queryFactory
.select(post.count())
.from(post)
.leftJoin(post.user, user)
.where(
searchKeywordContains(searchOptions.getSearchType(), searchOptions.getSearchKeyword()),
createdDateBetween(searchOptions.getStartDate(), searchOptions.getEndDate()),
viewCountsGoe(searchOptions.getMinViewCounts()),
commentCountsGoe(searchOptions.getMinCommentCounts()),
likesGoe(searchOptions.getMinLikes())
)
.fetchOne();
return new PageImpl<>(content, pageable, count != null ? count : 0L);
}
}
📌 중요한 점
- PostRepositoryImpl 클래스명은 반드시 PostRepositoryCustom 인터페이스명 + Impl 형식이어야 합니다.
- Spring Data JPA는 Repository 인터페이스와 같은 패키지 내에서 Impl 접미사가 붙은 클래스를 자동으로 인식합니다. (커스텀 할 수 있는 방법은 있음)
- @Repository는 없어도 자동으로 스캔됩니다.
- 주로 QueryDSL을 활용하여 동적 쿼리를 작성할 때 이용합니다.
3️⃣ 기존 Repository와 결합
public interface PostRepository extends JpaRepository<Post, Long>, SearchPostRepository {
}
📌 PostRepository가 JpaRepository를 상속받으면서 SearchPostRepository 도 함께 상속합니다.
➡️ 즉, 기본적인 CRUD 기능 + 커스텀 기능이 함께 제공됩니다.
Spring Data JPA가 커스텀 Repository를 인식하는 원리
Spring Data JPA는 어떻게 SearchPostRepositoryImpl을 자동으로 인식할까요?
Spring Data JPA의 내부 동작 방식은 다음과 같습니다.
1️⃣ Spring이 PostRepository 인터페이스를 감지
- Spring이 @SpringBootApplication을 실행할 때, 모든 JpaRepository를 스캔합니다.
- PostRepository는 JpaRepository와 SearchPostRepository을 상속받고 있으므로, 이를 기반으로 프록시 객체(Proxy Bean)를 생성합니다.
2️⃣ PostRepositoryCustom을 구현하는 SearchPostRepository을 찾음
- Spring은 SearchPostRepository을 구현한 클래스를 찾습니다.
- 이름 규칙 ( SearchPostRepository + Impl)에 맞는 SearchPostRepositoryImpl 을 자동으로 감지합니다.
3️⃣ PostRepositoryImpl을 PostRepository의 구현체로 주입
- SearchPostRepositoryImpl 이 감지되면, Spring은 내부적으로 PostRepository 프록시 객체를 생성하고, SearchPostRepositoryImpl 의 메서드를 포함시킵니다.
- 결과적으로 PostRepository를 주입받는 곳에서 SearchPostRepositoryImpl 의 메서드를 사용할 수 있게 됩니다.
📌 간단히 요약하면?
- Step 1: PostRepository 인터페이스 스캔
- Step 2: SearchPostRepository 인터페이스 확인
- Step 3: SearchPostRepositoryImpl 클래스 매칭
- Step 4: SearchPostRepositoryImpl 을 PostRepository 프록시 객체에 포함
커스텀 Repository를 활용하는 코드 예제
이제 우리가 만든 PostRepository를 사용해보겠습니다.
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
@Transactional(readOnly = true)
public Page<PostSummaryResponse> getAllPostsWithSearchOptionsToPage(Pageable pageable,
PostSearchOptions searchOptions) {
return postRepository
.findPostsBySearchOptionsToPage(pageable, searchOptions)
.map(PostSummaryResponse::from);
}
}
📌 PostService에서 PostRepository를 주입받아 사용하지만, 자동으로 SearchPostRepositoryImpl 의 메서드도 포함됩니다.
➡️ 즉, PostRepository.findPostsBySearchOptionsToPage()을 호출하면, 실제로는 SearchPostRepositoryImpl .findPostsBySearchOptionsToPage()이 실행됩니다!
결론: 커스텀 Repository의 동작 방식
1️⃣ Spring Data JPA는 JpaRepository 인터페이스를 자동으로 스캔한다.
2️⃣ PostRepositoryCustom과 같은 커스텀 인터페이스가 정의되었는지 확인한다.
3️⃣ PostRepositoryImpl이 존재하면 이를 자동으로 매핑하여 구현체로 사용한다.
4️⃣ PostRepository를 주입받으면, JpaRepository의 기본 기능 + PostRepositoryImpl의 커스텀 기능이 함께 제공된다.
즉, Spring Data JPA는 커스텀 Repository 구현체를 자동으로 감지하고, 이를 프록시 객체로 등록하여 개발자가 쉽게 사용할 수 있도록 해준다!
'Web > Spring, SpringBoot' 카테고리의 다른 글
테스트에서 Entity vs DTO 이용 (0) | 2025.04.02 |
---|---|
Spring @ModelAttribute 매핑 오류 해결 방법 (Setter, Enum 변환) (0) | 2025.03.20 |
스프링 부트 테스트 시 JPA Auditing 에러 (0) | 2025.03.12 |
Mockito를 이용한 테스트 vs. SpringBootTest의 차이점 (0) | 2025.03.05 |
application.properties 민감한 정보 보호하는 방법 (0) | 2025.02.13 |