공부일기
[Spring Data JPA] findBy, @Query정리 본문
이번 프로젝트를 진행하면서 진짜 제일제일제일루다가 신기했던 부분은 Repository 계층에서 직접 쿼리를 작성하지 않아도 쿼리가 동작한다는 점이었다.
예를 들어 이런 코드가 있었다.
public interface CommentRepository extends JpaRepository<Comment, Long> {
Page<Comment> findByPostId(Long postId, Pageable pageable);
}
처음에는 이런 의문이 들었다.
- 구현 코드가 없는데 왜 동작하지?
- findByPostId는 왜 post.id로 해석되지?
- Pageable은 누가 만들어서 넣어주지?
- @Query와 findBy...는 언제 다르게 써야 하지?
이번 글에서는 Spring Data JPA의 쿼리 방식을 MiniSNS 프로젝트에서 실제로 사용한 코드 기준으로 정리해보려고 한다.
1. Spring Data JPA가 Repository를 어떻게 구현 없이 동작시키는가
MiniSNS의 Repository는 대부분 이런 형태였다.
public interface PostRepository extends JpaRepository<Post, Long> {
}
인터페이스에는 구현 클래스가 아무것도 없는데, save() , findById() , findAll() 같은 메서드가 동작한다.
Spring Data JPA는 Repository 인터페이스를 기반으로 런타임에 구현체를 생성해 준다.
공식 문서에서도 Spring Data JPA는 JPA 기반 데이터 접근을 위해 일관된 프로그래밍 모델과 Repository 지원을 제공한다고 설명한다.
즉 개발자는
- SQL 직접 작성
- EntityManager 직접 조작
- CRUD 구현체 작성
같은 반복 작업을 많이 줄일 수 있다.
2. 메서드 이름만으로 쿼리 만들기
이건 메서드 이름을 분석해서 Spring Data JPA가 쿼리를 자동 생성하는 방식이다.
공식 문서에서는 이를 query creation from method names 라고 설명한다.
Page<Comment> findByPostId(Long postId, Pageable pageable);
아래와 같이 해석이 된다.
find | By | PostId
find -> 조회한다, By-> 조건 시작, PostId -> post.id 를 기주으로 조회한다.
여기서 중요한 포인트는:
Spring Data JPA는 DB 컬럼명이 아니라 엔티티 필드명을 기준으로 해석한다.
예를 들어 Comment 엔티티가 이렇게 되어 있다면:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
Repository 메서드 이름은 findBypost_id가 아니라 findByPostId가 된다.
왜냐하면 기준은 post_id 컬럼이 아니라 엔티티 필드인 post 이기 때문이다.
처음에는 "얼ㄹ렐렐방구,,,,, Comment 엔티티에는 postId 필드가 없는데 왜 되는거지?" 라는 의문이 들었었다.
하지만 Spring Data JPA는 postId로 다음처럼 해석한다.
post.id
3. 언제 findBy... 만으로는 부족한가
Spring Data JPA의 Derived Query는 편리하지만, 한계도 있다.
@Query("""
select new com.example.minisns.dto.CommentResponse(
c.id,
c.post.id,
c.user.id,
c.user.username,
c.content
)
from Comment c
where c.post.id = :postId
""")
Page<CommentResponse> findResponsesByPostId(@Param("postId") Long postId, Pageable pageable);
- JOIN이 필요하고
- DTO를 직접 생성해야 하고
- 어떤 필드를 어떻게 가져올지 명확히 제어해야 한다
그래서 이런 경우에는 @Query를 사용한다.
Spring Data JPA 공식 문서도 단순한 쿼리는 메서드 이름 기반으로 처리할 수 있지만, 더 복잡한 경우 @Query 같은 명시적 쿼리를 사용한다고 설명한다.
4. @Query는 무엇인가
@Query는 Repository 메서드에 직접 JPQL 또는 SQL을 지정하는 어노테이션이다.
MiniSNS에서는 위에서 본 것처럼 JPQL을 사용했다.
SQL
- 테이블명 사용
- 컬럼명 사용
예:
SELECT c.id, c.post_id, u.username
FROM comment c
JOIN users u ON c.user_id = u.id
WHERE c.post_id = 1;
JPQL
- 엔티티명 사용
- 엔티티 필드명 사용
예:
from Comment c
where c.post.id = :postId
즉 JPQL은 객체 중심 쿼리 언어라고 이해하면 된다.
5. :postId와 @Param("postId")
MiniSNS를 하면서 가장 많이 헷갈렸던 부분 중 하나가 이것이었다.
where c.post.id = :postId
여기서 :postId는 쿼리 안의 변수 자리(named parameter) 다.
그리고 Repository 메서드의 파라미터는 이렇게 연결된다.
@Param("postId") Long postId
다음과 같은 관계로 연결이 된다.
JPQL 변수 :postId
<- @Param("postId")
<- 자바 변수 Long postId
따라서 @Param("postId")의 "postId"와 ':psotId' 가 일치해야함.