내가 공부한 것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼 겸 상세히 기록하고 얕은 부분들은 가볍게 포스팅하겠습니다.
Spring Data JPA는 공통적인 기능이 아닌, 도메인에 특화된 기능을 지원하기 위해 쿼리 메소드를 지원합니다!
쿼리 메소드 기능 3가지
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
- @Query 어노테이션을 사용해서 repository interface에 쿼리 직접 정의
1. 메소드 이름으로 쿼리 생성
1-1. 순수 JPA 리포지토리 (MemberJpaRepository)
우선 직접 JPQL로 구현한 코드는 다음과 같다. 특정 username과 age 를 초과하는 사람을 검색하는 쿼리이다.
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
public List<Member> findByUsernameAndGreaterThen(String username, int age) {
return em.createQuery("select m from Member m" +
" where m.username = :username and m.age > :age"
).setParameter("username", username)
.setParameter("age", age)
.getResultList();
}
}
이를 사용하는 테스트 코드는 다음과 같다.
@Test
public void findByUsernameAndGreaterThen_test() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("AAA", 20);
memberJpaRepository.save(m1);
memberJpaRepository.save(m2);
List<Member> results = memberJpaRepository.findByUsernameAndGreaterThen("AAA", 15);
assertThat(results.get(0).getUsername()).isEqualTo("AAA");
assertThat(results.get(0).getAge()).isEqualTo(20);
assertThat(results.size()).isEqualTo(1);
}
위 테스트는 정상적으로 동작하며 실행되는 쿼리 또한 다음과 같다. JPQL이 sql로 번역된 코드이다.
select member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from member member0_
where member0_.username = 'AAA' and member0_.age > 15;
코드는 문제가 없다!
문제는 위 코드를 직접 작성해야 한다는 점이 문제이다...
메서드 이름만 봐도 어떤 동작을 수행할지 알것 같은데? 알아서 해주면 안될까?
Spring Data JPA는 이름으로 알아서 해준다!!
1-2. Spring Data JPA 의 쿼리메서드
다음 코드를 살펴보자.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
MemberRepository 인터페이스에 딱 1줄 추가하였다.
지정된 규칙을 따르는 이름으로 함수를 작성하면 구현체를 Spring Data JPA가 만들어준다.
이전과 동일한 Test를 실행하면 정상적으로 동작한다. (MemberJpaRepository 에서 MemberRepository로 변경)
@Test
public void findByUsernameAndGreaterThen_test() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("AAA", 20);
memberRepository.save(m1);
memberRepository.save(m2);
List<Member> results = memberRepository.findByUsernameAndAgeGreaterThan("AAA", 15);
assertThat(results.get(0).getUsername()).isEqualTo("AAA");
assertThat(results.get(0).getAge()).isEqualTo(20);
assertThat(results.size()).isEqualTo(1);
}
테스트가 정상적으로 통과하며, 이전과 같은 SQL문이 나가게 된다.
메서드 이름으로 쿼리를 만드는 것 이다.
정해진 문법에 적합하게 프로퍼티 이름을 명시하면서 사용해야 한다. 이름이 조금만 달라져도 작동하지 않는다.
API 공식 문서 - https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
다만 이렇게 되면 조건이 늘어날때 마다 메서드의 이름이 길어지게 된다.
따라서 보통 조건 2개까지는 쿼리메서드를 사용하지만, 3개 이상부터는 다른 방식으로 해결한다.
스프링 데이터 JPA가 제공하는 쿼리 메소드 기능
- 조회: find...By ,read...By ,query...By get...By, 예:) findHelloBy 처럼 ...에 식별하기 위한 내용(설명)이 들어가도 된다.
- COUNT: count...By 반환타입 long
- EXISTS: exists...By 반환타입 boolean
- 삭제: delete...By, remove...By 반환타입 long
- DISTINCT: findDistinct, findMemberDistinctBy
- LIMIT: findFirst3, findFirst, findTop, findTop3
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
보통 네임드쿼리는 짧은 간단한 쿼리를 만들때 사용한다. 복잡한 조건이 걸려있는 메서드를 만들때는 사용하지 않는다.
참고: 이 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 한다.
그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다.
2. JPA NamedQuery
이번 기능은 사실 실무에서 거의 사용하지 않는다.
1. Entity에 @NamedQuery를 적용
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username", "age"})
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
// 생략...
}
2. 순수 JPA를 이용한 NamedQuery를 호출할 수 있다.
public List<Member> findByUsername(String username) {
return em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
}
3. Spring Data JPA를 이용한 NamedQuery 사용하기
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
}
테스트 코드는 이전과 동일하게 동작한다.
추가로 namedQuery에는 @Query를 생략해도, 메서드 이름만으로 namedQuery를 호출할 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(@Param("username") String username);
}
어떻게 가능한 것 일까?
3-1. Spring Data JPA는 선언한 "도메인 클래스 + .(점) + 메서드 이름"으로 Named 쿼리를 찾아서 실행한다.
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
우리의 코드에서는 "Member.findByUsername"이 된다. findByUsername이 메서드 이름과 동일하기 때문에 사용 가능하다.
3-2. 만약 찾는 이름의 name이 없으면, 메서드 이름으로 쿼리를 생성하는 쿼리메서드로 동작하게 된다.
즉, namedQuery를 찾는것이 우선순위가 더 높다.
하지만 namedQuery는 실무에서 거의 사용되지 않는다.
일단 사용하기가 불편하고, repository의 메서드 위에 바로 query를 작성하는 기능을 더 많이 사용하기 때문이다.
다만 namedQuery에도 정말 큰 장점이 1가지가 있다.
예를 들어 다음 코드에서 "m.username!!!!21" 와 같이 오타가 있다고 해보자.
public List<Member> findByUsernameAndGreaterThen(String username, int age) {
return em.createQuery("select m from Member m" +
" where m.username!!!!21 = :username and m.age > :age"
).setParameter("username", username)
.setParameter("age", age)
.getResultList();
}
JPQL은 정상적으로 실행이 된다. findByUsernameAndGreaterThen메서드 외에 다른 기능을 사용할때는 잘 작동한다.
em.createQuery() 안에는 단순 문자열이 들어가기 때문에 문법 오류를 잡을수 없다.
문제는 고객이 findByUsernameAndGreaterThen메서드의 기능을 누르는 순간 오류가 발생한다.
이에 반해 namedQuery는 오타가 있으면 에플리케이션 로딩 시점에 해당 jpql을 분석한 후, 오류가 있으면 문법 오류를 알려준다.
다음과 같이 @NamedQuery에 "m.usernameError나는부분" 처럼 오타가 있다고 해보자.
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.usernameError나는부분 = :username"
)
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
// 생략...
}
에러가 발생하게 된다.
NamedQuery는 기본적으로 정적 쿼리 이기 때문에 에플리케이션 로딩 시점에 다 parsing 해볼 수 있다.
따라서 JPQL을 SQL로 미리 다 만들어 놀 수 있게된다.
문법에 오류가 있다면 미리 거를수 있는 것 이다!
3. @Query, 리포지토리 메소드에 쿼리 정의하기
@Query 기능을 사용하면 메서드 위에 바로 JPQL문을 작성할수가 있다!
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :username and m.age = :age")
List<Member> findUser(@Param("username") String username, @Param("age") int age);
}
메서드 위에 바로 쿼리문을 작성하는 것 이다!!
- 실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있음
- JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음
'BackEnd > JPA' 카테고리의 다른 글
[JPA] 쿼리 메소드 기능 - 3 (0) | 2022.05.04 |
---|---|
[JPA] 쿼리 메소드 기능 - 2 (0) | 2022.05.04 |
[JPA] 공통 인터페이스 기능 (0) | 2022.05.03 |
[JPA] 예제 도메인 모델 (0) | 2022.05.02 |
[JPA] Open Session In View (OSIV) (0) | 2022.04.24 |
댓글