Spring Data JPA 에서 제공하는 공통 기능 말고도, 예를 들어 username으로 조회를 해 온다던지, 이런 도메인에 특화된 조회 등 공통되지 않은 부분들을 만들어야 할 필요성이 있다.
Spring Data JPA는 이러 한 공통적인 부분이 아닌 쿼리들을 어떻게 만들도록 지원해 주는지 알아볼 것이다.
Spring Data JPA에서 쿼리를 만들도록 지원해 주는 것은 총 3가지 방법이 있는데,
1. 메소드 이름으로 쿼리 생성
2. JPA NamedQuery 호출
3. @Query 어노테이션을 사용해서 리파지토리 인터페이스에 쿼리 직접 정의
먼저 메소드 이름으로 쿼리 생성하는 것에대해 알아볼 거임.
그 전에, Jpa로만 먼저 짜보면
username이 같고 age가 특정 값 보다 큰거
public List<Member> findByUsernameAndAgeGraterThen(String username, int age){
return em.createQuery("select m from Member m where m.username = :username and m.age > :age", Member.class)
.setParameter("username", username)
.setParameter("age", age)
.getResultList();
}
이렇게 된다.
@Test
public void findByUsernameAndAgeGraterThen(){
Member member1 = new Member("AAA",10);
Member member2 = new Member("AAA",20);
memberJpaRepository.save(member1);
memberJpaRepository.save(member2);
List<Member> findMembers = memberJpaRepository.findByUsernameAndAgeGraterThen("AAA", 15);
findMembers.forEach((Member member)->{
assertThat(member.getUsername()).isEqualTo("AAA");
assertThat(member.getAge()).isGreaterThan(15);
});
assertThat(findMembers.size()).isGreaterThanOrEqualTo(1);
}
테스트 잘 된다.
나가는 쿼리
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
where
m1_0.username=?
and m1_0.age>?
2023-11-20T07:58:14.924+09:00 INFO 17176 --- [ main] p6spy : #1700434694924 | took 0ms | statement | connection 4| url jdbc:h2:tcp://localhost/./datajpa
select m1_0.member_id,m1_0.age,m1_0.team_id,m1_0.username from member m1_0 where m1_0.username=? and m1_0.age>?
select m1_0.member_id,m1_0.age,m1_0.team_id,m1_0.username from member m1_0 where m1_0.username='AAA' and m1_0.age>15;
2023-11-20T07:58:15.006+09:00 INFO 17176 --- [ main] p6spy : #1700434695006 | took 0ms | commit | connection 4| url jdbc:h2:tcp://localhost/./datajpa
;
이제 이거를 메소드 이름으로 쿼리를 생성하는 것을 해 볼것이다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
이렇게 했다.
따로 구현한 게 없다.
그냥 이름만 약속에 맞게 잘 적어줬다. 그러면,
@Test
public void findByUsernameAndAgeGraterThen(){
Member member1 = new Member("AAA",10);
Member member2 = new Member("AAA",20);
memberRepository.save(member1);
memberRepository.save(member2);
List<Member> findMembers = memberRepository.findByUsernameAndAgeGreaterThan("AAA", 15);
findMembers.forEach((Member member)->{
assertThat(member.getUsername()).isEqualTo("AAA");
assertThat(member.getAge()).isGreaterThan(15);
});
assertThat(findMembers.size()).isGreaterThanOrEqualTo(1);
}
리포지토리를 바꿔서 실험을 해봐도, 똑같은 결과가 나온다.
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
where
m1_0.username=?
and m1_0.age>?
나가는 쿼리도 같다.
진짜 메소드 명으로 자동으로 만들어 진 것이다.
만약
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> hi(String username, int age);
}
그냥 이렇게 대충하면
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'hi' found for type 'Member'; Did you mean 'id'
뭐 Member에서 hi를 찾을 수 없다고 뜬다. 필드로 읽어보려고 시도해 본 모양이다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
천천히 읽어보면,
findBy 찾다
Username 유저네임으로
And 그리고
AgeGreaterThen 나이가 더 큰걸로
그럼 그 규칙이 뭐냐?
https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html
여기에 나온다.
예시와 만들어지는 이름까지 잘 설명해 놨다.
아무래도 다 조회에 대한 것 밖에 없다.
하긴 CUD는 아예 JPQL 쿼리를 직접 안 써도 제공해 주니, 조회만 있어도 될 거 같다.
여기 중 하나에
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
이런 것도 있다.
in 해서 in에 들어갈 것을 컬렉션으로 그냥 넘기면 된다.
KeywordSampleJPQL snippet
Distinct | findDistinctByLastnameAndFirstname | select distinct … where x.lastname = ?1 and x.firstname = ?2 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is, Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull, Null | findByAge(Is)Null | … where x.age is null |
IsNotNull, NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
name이나 age는 그냥 예시일 뿐이고, 당연히 다른 것으로 해도 된다.
위의 것들은 거의 조건? 에 관한 것 들이고,
countBy : long타입으로 몇개 있는지 반환
existsBy : 존재하는지 bool 타입으로 반환
deleteBy 또는 removeBy : 삭제. 반환타입 long. 몇개의 row가 영향을 받았는지 그거일 듯.
findMemberDistictBy : 이렇게 distinct도 가능.
또 이름으로 페이징도 가능
top이나 first로
findTop3ByAge() : 제일 위에서 3개
findFirst3ByAge() : 제일 첫번째부터 3개
List<Member> findTop3ByAge(int age);
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
where
m1_0.age=?
fetch
first ? rows only
근데 Top이나 First나 나가는 쿼리는 같음. 그냥 findBy를 readBy라고도 할 수 있고 getBy, queryBy라고도 할 수 있는 그런 비슷한 거 인듯.
orderBy도 할 수 있음
findFirst3ByOrderByAgeAsc()
age를 Asc순으로 한거 처음 3개만.
참고로, findFirstBy 그냥 이렇게 할 수 있는데
이러면 하나만 가져오는 거임. 가정 First 하나 아니면 가장 Top 하나 등.
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
예시들
그리고
findBy할 때 저 find와 By 사이에 자유롭게 넣을 수 있음.
나는 뭐 find 다음 가져올 필드 이런 거 넣는 거 인 줄 알았는데 아님.
그냥 식별? 할수 있게끔 정말 자유롭게 쓸 수 있는거임.
예를들어 findRankerBy~~~
그냥 저렇게 개발자가 식별할 수 있게끔.
findHelloBy 이런 것도 됨.
페이징(Top, First) 제외하고, 만들어지는 쿼리에 영향이 없음. 그냥 순전히 개발자가 보고 식별? 메모하는 용도임.
참고로,
List<Member> findBy();
그냥 이렇게도 가능.
이렇게 하면 그냥 전체 조회임.
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
그냥 select * from 근원
find...By()
// ...에 페이징 가능
findFirst3By()
// 처음 3개 가져옴.
findFirst3ByOrderByAgeAsc()
// 처음 3개. 그런데 order By Age asc 된거에서
findByAge(int age)
//where age = :age, 즉, age에 의해 찾기.
findByNameAndAge(String name, int age)
// select m from Member m where name = :name and age = :age
//findBy 뒤에 필드 붙으면 조건.
//orderBy등 명령 붙으면 그 명령.
findFirst3ByOrderByAgeAsc()
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
order by
m1_0.age
fetch
first ? rows only
order by age asc
asc가 기본이니까 굳이 그렇게 안나간거고,
first 3 이니까 limit(first)으로 3개
얘는 기본적으로 Dto나 특정값들이 아니라,
엔티티 자체를. 엔티티 전체 자체를 가져오는 게 기본인 듯.
되게 짧은 간단한 쿼리들 만들 때 좋은 듯.
참고로, 중요한데
엔티티의 필드명이 변경되면 메소드명도 변경되어야 함.
메소드명을 보고 쿼리를 만들 때 엔티티의 필드명을 참조하는 거니..
JPQL이니 엔티티 참조니까.
'스프링데이터 + JPA > 스프링 데이터 JPA' 카테고리의 다른 글
11. @Query, 메소드에 쿼리 정의 (0) | 2023.11.20 |
---|---|
10. JPA 네임드 쿼리 (0) | 2023.11.20 |
8. 공통 인터페이스 분석 (0) | 2023.11.19 |
7. 공통 인터페이스 적용 (0) | 2023.11.19 |
6. 공통 인터페이스 설정 (0) | 2023.11.19 |