지난 포스팅에서는 Spring Data JPA를 이용해서 JPA를 편리하게 사용하는 방법에 대해 알아봤었어요. 하지만 JpaRepository의 기능만으로는 실무의 모든 기능들을 구현할 수 없기 때문에, 로직에 맞는 함수를 정의해야 할 때가 있어요. 오늘은 그 방법인 쿼리 메서드에 대해 알아보려 합니다
개요
예제 도메인 모델과 모델에 맞는 Entity, Repository는 지난 포스팅에서 작성했어요. 이어서 진행하기 때문에, 이번 포스팅만 보시면 이해하기 어려우실 수 있습니다😅
포스팅에서는 간단한 예제만 다룹니다. 조금 더 정확한 메서드들과 사용 방법은 해당 래퍼런스에서 확인해주세요!
JPQL📔
JPQL은 Java Persistence Query Language로 JPA를 사용하여 엔티티 객체를 중심으로 개발할 때 사용합니다. 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색을 해야 해요. 엔티티를 바라보기 때문에 특정 데이터베이스에 의존하지 않아서 DB 변경도 쉽게 이루어집니다.
JPA는 JPQL을 분석한 후 DB 방언에 맞게 적절한 SQL을 만들어 데이터베이스를 조회합니다. 사전에 JPQL을 설명한 이유는 지금부터 쿼리 메서드가 어떻게 동작하는지에 대한 사전 설명이 필요하다고 생각했어요.
이후부터 설명할 메서드 이름으로만 쿼리 생성하기, @Query 에노테이션, 페이징 처리 등의 함수들이 작동할 수 있는 것은 모두 Entity를 대상으로 작성되기 때문입니다. 프로그램과 데이터베이스 사이에서 영속성 컨텍스트라는 공간이 존재하고, 프로그램에서 엔티티를 대상으로 JPQL을 작성하면 JPA가 영속성 콘텍스트에서 연결된 데이터베이스에 맞게 SQL을 대신 작성한다고 생각하시면 이해하시기 편할 것 같네요😄
메서드 이름으로 쿼리 생성📗
만약 같은 이름의 멤버들을 가져오는 함수를 만든다고 가정했을 때, 순수 JPA로만 작성하려면 직접 JPQL을 작성해야 합니다.
public List<Member> findByUsername(String username) {
List<Member> memberList =
em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", username)
.getResultList();
return memberList;
}
위 코드처럼 직접 createQuery를 사용하여 로직에 맞게 작성하면 되지만, 실무에서 이렇게 자주 쓰는 코드들이 정말 많기에 Spring Data JPA에서는 이름으로만 쿼리를 만들 수 있게 지원해줍니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// 메소드 이름으로 쿼리 생성
List<Member> findByUsername(String username);
}
JpaRepository를 확장한 MemberRepository 인터페이스에서 findBy + 조건으로 메서드를 작성하면 위에서 만든 JPQL과 똑같이 작동하게 구현이 됩니다.
이밖에도 가장 먼저 조회되는 3명의 멤버의 데이터만 가져오고 싶을 때는 아래와 같이 메서드를 작성하면 됩니다.
List<Member> findByTop3();
조건문이 많으면 어떻게 될까요? 만약 유저 이름이 같고 원하는 나이보다 많은 멤버의 데이터만 가져오고 싶다면요?
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
조건이 2개 인대도 벌써 메서드 이름이 상당히 길어지는 것을 볼 수 있습니다. 하지만 실무에서는 상세 검색 등 조건이 많아지는 경우가 허다합니다. 그럴 때마다 And, Or를 쓰면서 작성하기도 어렵고, 무엇보다 유지보수 측면에서 상당히 어려움을 느끼게 됩니다.
그래서 간단한 조건들은 메서드 이름으로 정의하지만, 조건이 많은 경우나 지원하지 않는 기능이라면 @Query 애노테이션을 사용해서 직접 작성해야 합니다.
@Query 사용하기📒
사용자 정의 쿼리란 말 그대로 JPA가 자동으로 생성하는 쿼리를 사용하는 게 아닌 사용자가 정의한 대로 쿼리가 생성 혹은 데이터베이스에 종속적인 Native Query가 생성되는 것을 말해요.
물론 동적으로 쿼리를 생성할 수는 없고 @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);
위에서 메서드 이름으로 만들었던 findByUsernameAndAgeGreaterThan() 메서드와 차이점을 살펴보자면(나이가 많다, 같다 제외) 이름으로만 작성한 메서드는 앞서 언급드렸듯이, 메서드 이름 자체가 길어서 유지보수 측면에서 어려움을 느낄 수 있어요.
또한, @Query를 사용하면 사용자가 임의대로 쿼리문을 조작할 수 있는 장점과 쿼리문 안에 프로퍼티 오타가 있을 때 애플리케이션 실행 시 오류로 잡아주는 장점 또한 존재합니다.
@Test
public void testQuery() {
Member binco = new Member("Binco", 27);
memberRepository.save(binco);
List<Member> result = memberRepository.findUser("Binco", 27);
Member findUser = result.get(0);
assertThat(findUser).isEqualTo(binco);
}
테스트 코드를 작성했을 때 쿼리가 어떻게 날아가는지 살펴볼까요?
문제없이 원하는 로직대로 쿼리가 나가는 것을 확인하실 수 있어요. 또한 테스트 케이스도 성공하는 것을 확인하실 수 있으실 거예요😀
이 밖에도, Collection In절 처리도 가능합니다.
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") Collection<String> names);
코드만 봐도 어떤 식으로 작동하는지 이해가 되실 것 같아서 테스트 케이스는 생략할게요 :)
DTO로 직접 조회를 원한다면 DTO를 생성한 다음에 쿼리문 안에 JPA의 new 오퍼레이션을 사용하여 DTO로 추출해야 합니다.
@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;
}
}
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
원하는 DTO를 생성하고 쿼리문에 맞는 생성자를 추가한 다음에 @Query안에 DTO를 입력해주시면 됩니다😄
마지막으로 Spring Data JPA는 유연한 반환 타입을 지원합니다.
List<Member> findListByUsername(String username);
Optional<Member> findOptionalByUsername(String username);
Member findMemberByUsername(String username);
예시로 컬렉션과 단건 타입을 들었어요. List 컬렉션은 데이터가 없을 경우에는 Empty 컬렉션을 반환합니다. Null이 아님을 주의해주세요!
Optional은 단건 조회이면서 null 일수도 아닐 수도 있을 경우에 사용합니다. 하지만 만약 결과가 2개 이상이면 당연히 오류가 나겠죠?
Entity로 반환하는 것은 단건 조회이면서 데이터가 없을 경우에 Null을 반환합니다.
더 많은 반환타입을 알고 싶으시다면 하단 레퍼런스를 참고해주세요!
👨👩👦👦 오픈채팅방 운영
취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁
참여코드 : 456456
https://open.kakao.com/o/gVHZP8dg
👨💻 전자책 출간
아울러 제가 🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁
마치며
지금까지 Spring Data JPA의 쿼리 메서드에 대해 알아보았습니다. 기본적으로 제공해주는 메서드들과 사용자가 직접 정의하는 방법들까지 알아보았습니다. 다음 포스팅은 Spring Data JPA가 제공하는 강력한 페이징 기술에 대해 포스팅할게요😄
Spring Data JPA Series
1. Spring Data JPA와 기존 JPA의 차이점 및 사용법
4. Spring Data JPA Bulk Update & Entity Graph
Reference
'JPA' 카테고리의 다른 글
Spring Data JPA Bulk Update & Entity Graph (0) | 2022.10.14 |
---|---|
Spring Data JPA 페이징 처리 (0) | 2022.10.14 |
Spring Data JPA와 기존 JPA의 차이점 및 사용법 (0) | 2022.10.13 |
SpringBoot JPA 쇼핑몰 주문 검색 (0) | 2022.09.19 |
SpringBoot JPA 쇼핑몰 주문 서비스 II (0) | 2022.09.15 |