김영환님의 강의 실전! 스프링 데이터 JPA 보면서 공부한 내용입니다.
드 기능
@Query 어노테이션을 사용해서 리파지토리 인터페이스에 쿼리 직접 정의✅ 메소드 이름으로 쿼리 생성
// MemberJpaRepository.java
public List<Member> findByUsernameAndAgeGreaterThen(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 findByUsernameAndAgeGreaterThen() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("AAA", 20);
memberJpaRepository.save(m1);
memberJpaRepository.save(m2);
List<Member> result = memberJpaRepository.findByUsernameAndAgeGreaterThen("AAA", 15);
assertThat(result.get(0).getUsername()).isEqualTo("AAA");
assertThat(result.get(0).getAge()).isEqualTo(20);
assertThat(result.size()).isEqualTo(1);
}

📝 문제점
// MemberRepository.java
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
// 쿼리 직접 작성 X
// 인터페이스만 만들고 구현체 안만듬
// 테스트 동일

💡 관례
- Username And Age => 관계조건으로 묶임
- Username이 equal로 컨디션을 먹임
- AgeGreaterThan 은 파라미터 조건보다 크면 이라고 되어있음
- 이름을 다르게하면 안먹힘
- 엔티티 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 함께 변경해야함
→ 변경하지 않을 시 애플리케이션을 시작하는 시점에서 오류 발생
💡쿼리 메소드 필터 조건 스프링 데이터 JPA 공식 문서 참고 https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query-methods.query-creation
📝 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능
find...By... ,read...By, query...By, get...ByfindHelloBy 처럼 ...에 식별하기 위한 내용(설명)이 들어가도 된다. findHelloBy는 By 뒤에 아무것도 안들어가있으므로 전체 조회됨count…By → 반환타입 longexists…By → 반환타입 booleandelete…By, remove…By → 반환타입 longfindDistinct, findMemberDistinctByfindFirst3, findFirst, findTop, findTop3✅ JPA NamedQuery
→ 실무에서 거의 쓸 일 없음 ㅎㅎ
📝 장점
📝 단점
@NamedQuery(
name="Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {}
// MemberRepository.java
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
// MemberJpaRepository.java
public List<Member> findByUsername(String username){
return em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
}
📝 장점
// @Query 어노테이션
@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);
// @Query 어노테이션 (값 조회)
@Query("select m.username from Member m")
List<String> findUsernameList();
// @Query 어노테이션 (DTO 조회)
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
📝 종류
select m from Member m where m.username = ?1select m from Member m where m.username = :username // 컬렉션 파라미터 바인딩
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") Collection<String> names);
// 반환 타입
// 반환 타입 유연하게 사용 가능
List<Member> findListByUsername(String username); // 컬렉션
Member findMemberByUsername(String username); // 단건
Optional<Member> findOptinalByUsername(String username); // 단건 Optional
📝 정렬 예제
// MemberJpaRepository
// 페이지 정렬
public List<Member> findByPage(int age, int offset, int limit){
return em.createQuery("select m from Member m where m.age = :age order by m.username desc")
.setParameter("age", age)
.setFirstResult(offset) // 어디서부터 가져올꺼야
.setMaxResults(limit) // 갯수를 몇개 가져올꺼야
.getResultList();
}
public long totalCount(int age){
return em.createQuery("select count(m) from Member m where m.age = :age", Long.class)
.setParameter("age", age)
.getSingleResult();
}
@Test
public void paging() {
// given
memberJpaRepository.save(new Member("member1", 10));
memberJpaRepository.save(new Member("member2", 10));
memberJpaRepository.save(new Member("member3", 10));
memberJpaRepository.save(new Member("member4", 10));
memberJpaRepository.save(new Member("member5", 10));
int age = 10;
int offset = 0;
int limit = 3;
//when
List<Member> members = memberJpaRepository.findByPage(age, offset, limit); // 0번부터 3개를 뽑음
long totalCount = memberJpaRepository.totalCount(age);
// then
assertThat(members.size()).isEqualTo(3);
assertThat(totalCount).isEqualTo(5);
}
📝 페이징과 정렬 파라미터
org.springframework.data.domain.Sort : 정렬 기능org.springframework.data.domain.Pageable : 페이징 기능 (내부에 Sort 포함)📝 특별한 반환 타입
org.springframework.data.domain.Page : 추가 count 쿼리 결과를 포함하는 페이징 (totalCount를 필요로하는 페이지)org.springframework.data.domain.Slice : 추가 count 쿼리 없이 다음 페이지만 확인 가능(내부적으로 limit + 1조회, 더보기 버튼이 있는 페이지)List (자바 컬렉션): 추가 count 쿼리 없이 결과만 반환✅ Page
// 페이지 테스트
@Test
public void paging() {
// given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 10));
memberRepository.save(new Member("member3", 10));
memberRepository.save(new Member("member4", 10));
memberRepository.save(new Member("member5", 10));
int age = 10;
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
// pageNumber은 1이 아닌 0부터 시작
// pageSize 3 : 0페이지에서 3개 가져와
//when
Page<Member> page = memberRepository.findByAge(10, pageRequest); // 0번부터 3개를 뽑음
// 반환 타입을 페이지로 받으면 알아서 totalCount쿼리를 날려줌
// then
List<Member> content = page.getContent(); // 페이지에있는 값들 가져옴
long totalElements = page.getTotalElements();// totalCount
assertThat(content.size()).isEqualTo(3); // 조회된 데이터
assertThat(totalElements).isEqualTo(5); // 전체 조회된 테이터
assertThat(page.getNumber()).isEqualTo(0); // 페이지 번호
assertThat(page.getTotalPages()).isEqualTo(2); // 전체 페이지 갯수
assertThat(page.isFirst()).isTrue(); // 첫번째 페이지인가?
assertThat(page.hasNext()).isTrue(); // 다음 페이지가 있어?
}
3개를 가져오는 것을 확인할 수 있다
✅ Slice
// MemberRespository
Slice<Member> findByAge(int age, Pageable pageable);
// Test
@Test
public void paging() {
// given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 10));
memberRepository.save(new Member("member3", 10));
memberRepository.save(new Member("member4", 10));
memberRepository.save(new Member("member5", 10));
int age = 10;
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
// pageNumber은 1이 아닌 0부터 시작
// pageSize 3 : 0페이지에서 3개 가져와
//when
Slice<Member> page = memberRepository.findByAge(10, pageRequest); // 3개를 요청했지만 limit + 1 해서 총 4개를 요청함
// 그렇기 때문에 totalCount를 가져오지않음
// then
List<Member> content = page.getContent(); // 페이지에있는 값들 가져옴
assertThat(content.size()).isEqualTo(3); // 조회된 데이터
assertThat(page.getNumber()).isEqualTo(0); // 페이지 번호
assertThat(page.isFirst()).isTrue(); // 첫번째 페이지인가?
assertThat(page.hasNext()).isTrue(); // 다음 페이지가 있어?
}
4개를 요청한 것을 확인할 수 있다