일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Bean Validation
- 실무활용
- 페이징
- Spring Data JPA
- JPQL
- 로그인
- 임베디드 타입
- JPA
- 검증 애노테이션
- JPA 활용2
- 컬렉션 조회 최적화
- jpa 활용
- 스프링 mvc
- 값 타입 컬렉션
- 김영한
- 타임리프
- 일론머스크
- 예제 도메인 모델
- 스프링 데이터 JPA
- 스프링
- 트위터
- JPA 활용 2
- 기본문법
- 불변 객체
- QueryDSL
- 타임리프 문법
- 프로젝트 환경설정
- 벌크 연산
- 스프링MVC
- API 개발 고급
- Today
- Total
RE-Heat 개발자 일지
[Spring Data JPA] [4] 쿼리 메소드 기능(상편) 본문
인프런 김영한 님의 강의를 듣고 작성한 글입니다
■ 쿼리 메소드 기능 3가지
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
- @Query 어노테이션을 사용해서 리파지토리 인터페이스에 쿼리 직접 정의
[1] 메소드 이름으로 쿼리 생성
공통 기능 외에 다른 기능이 필요하다면?
■ 이름과 나이를 기준으로 회원을 조회하려면?
▷ 테스트 코드 - 순수 JPA와 스프링 데이터 JPA 적용한 테스트 모두 같은 코드
@Test
public void findByUsernameAndAgeGreaterThan(){
Member m1 = new Member("AAA", 10);
Member m2 = new Member("AAA", 20);
memberJpaRepository.save(m1);
memberJpaRepository.save(m2);
List<Member> result = memberJpaRepository.findByUsernameAndAgeGreaterThan("AAA", 15);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get(0).getUsername()).isEqualTo("AAA");
assertThat(result.get(0).getAge()).isEqualTo(20);
}
① 순수 JPA
MemberJpaRepository
public List<Member> findByUsernameAndAgeGreaterThan(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();
}
- 이렇게 짜면 되지만, 상당히 귀찮은 작업이다.
② 스프링 데이터 JPA 적용
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
- 스프링 데이터 JPA의 매뉴얼에서 제공하는 규칙에 맞게 메소드 이름을 짜면 우리가 원하는 쿼리문이 실행된다.
- 만일 맞추지 않으면(findByUsername2AndAgeGreaterThan) property가 틀렸다는 오류가 뜬다.
참고] 쿼리 메소드 필터 조건
스프링 데이터 JPA 공식 문서 확인 : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
■ 스프링 데이터 JPA가 제공하는 쿼리 메서드 기능
- 조회: find...By, read...By, query...By, get...By
- COUNT: count...By | 반환타입 long
- EXISTS: exists…By | 반환타입 boolean
- 삭제: delete…By, remove…By | 반환타입 long
- DISTINCT: findDistinct, findMemberDistinctBy
- LIMIT: findFirst3, findFirst, findTop, findTop3
예시]
① 조회 : find...By
② Limit
[2] JPA NamedQuery
@NamedQuery 애노테이션으로 Named Query 정의 후 사용하는 방식
Member
@Entity
@Getter @Setter
@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 {
}
- name에 이름 query에 JPQL을 미리 선언한다.
MemberJpaRepository - 순수 JPA
public List<Member> findByUsername(String username) {
return em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
}
- createNamedQuery에 정의해 놓은 이름을 호출해 사용할 수 있다.
- createNameQuery가 아닌 createQuery를 써 버리면 오류가 발생할 수 있으므로 주의
MemberJpaRepositoryTest
@Test
public void testNameQuery(){
Member m1 = new Member("AAA", 10);
Member m2 = new Member("AAA", 20);
memberJpaRepository.save(m1);
memberJpaRepository.save(m2);
List<Member> result = memberJpaRepository.findByUsername("AAA");
Member findMember = result.get(0);
assertThat(findMember).isEqualTo(m1);
}
MemberRepository - 스프링 데이터 JPA 적용
@Query(name = "Member.findByUsername") //생략 가능
List<Member> findByUsername(@Param("username") String username);
- @Param 애노테이션을 통해 매핑한다.
- Query문의 where username = :username과 "username"이 꼭 일치해야 한다.
@Query를 생략할 수 있다.
이유 : 스프링 데이터 JPA가 JPQL을 생성할 때 순서가 있기 때문
1. 엔티티.메소드 이름(Member.findByUsername)과 같은 이름의 @NamedQuery를 찾는다.
2. 없으면 메소드 이름으로 쿼리를 생성한다.
@NamedQuery
장점 : application 로딩 시점에 파싱할 수 있어 오류를 바로 찾을 수 있다.
하지만 리포지토리 메소드에 쿼리를 정의하는 방식이 더 좋아서 실무에서 쓰이는 일은 잘 없다.
[3] @Query, 리포지토리 메소드에 쿼리 정의하기
리포지토리 메소드에 바로 쿼리를 작성한다. 실행할 메소드에 바로 작성해 이름없는 Named Query라 봐도 무방하다.
MemberRepository
@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);
결론 :
1. 파라미터가 적을 땐 메소드 이름으로 쿼리 생성
2. 파라미터가 많을 땐 메소드 이름이 너무 길어지니 '@Query 레포지토리 메소드에 쿼리 정의하기'를 쓰면 된다.
[4] @Query, 값, DTO 조회하기
■ 단순히 값 조회하기
MemberRepository
@Query("select m from Member m")
List<String> findUsernameList();
- JPA 값 타입(@Embedded)도 이 방식으로 조회할 수 있다.
■ DTO로 직접 조회하기
MemberDto
@Data
public class MemberDto {
private Long id;
private String username;
private String teamName;
public MemberDto(Long id, String username, String teamName) {
this.id = id;
this.username = username;
this.teamName = teamName;
}
}
MemberRepository
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
- new를 쓸 때 Dto의 정확한 위치를 알려줘야 한다. Querydsl을 쓰면 훨씬 간편하다고 한다.
테스트와 SQL문
[5] 파라미터 바인딩
① 위치 기반
select m from Member m where m.username = ?0 //위치 기반
② 이름 기반
select m from Member m where m.username = :name //이름 기반
- 위치 기반은 거의 사용하지 않는다.
- 위치기반은 순서만 바뀌면 원하는 값을 얻질 못하게 된다.
- 코드 가독성 유지 보수성을 위해 이름 기반을 써야 한다.
■ 컬렉션 파라미터 바인딩
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") Collection<String> names);
- List<String>보다 Collection이 더 많이 받을 수 있으므로 Collection으로 변경
- in절로 더 많은 값을 한 번에 조회할 수 있다.
테스트 및 SQL문
- Arrays.asList("AAA", "BBB")이 값을 넘겨줘 한 where in "AAA", "BBB" 동작하게 만듦
[6] 반환 타입
스프링 데이터 JPA는 유연한 반환 타입을 지원한다.
List<Member> findListByUsername(String username); //컬렉션
Member findMemberByUsername(String username);//단건
Optional<Member> findOptionalByUsername(String username);//단건 Optional
※ 주의사항
- List는 값이 없으면 빈 컬렉션을 제공해 준다.
- 반면 단 건 조회는 null값이 반환된다.
단건으로 지정한 메서드를 호출하면 스프링데이터 JPA는 내부에서 JPQL의 Query.getSingleResult() 메서드를 호출한다. 이 메서드를 호출했을 때 조회 결과가 없으면 javax.persistence.NoResultException 예외가 발생하는데 개발자 입장에서 다루기가 상당히 불편하다.
그래서 스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신 null을 반환한다.
단, 이 건 Optional이 나오기 전 방법이다.
결론 : Optional은 결과가 있을 수도 없을 수도 있다는 걸 가정하므로 Optional을 쓰는 게 낫다.
■ 조회 결과가 많거나 없으면?
컬렉션
- 결과 없음 : 빈 컬렉션 반환
단건 조회
- 결과 없음 : null 반환
- 결과가 2건 이상 : javax.persistence.NonUniqueResultException 예외 발생
'백엔드 > 스프링 데이터 JPA' 카테고리의 다른 글
[Spring Data JPA] [5] 확장 기능 (0) | 2023.09.10 |
---|---|
[Spring Data JPA] [4] 쿼리 메소드 기능(하편) - 페이징·벌크연산·EntityGraph (0) | 2023.09.09 |
[Spring Data JPA] [3] 공통 인터페이스 기능 (0) | 2023.09.08 |
[Spring Data JPA] [2] 예제 도메인 모델 (0) | 2023.09.08 |
[Spring Data JPA] [1] 프로젝트 환경 설정 (0) | 2023.09.07 |