Search
Duplicate

쿼리 메소드 기능

메소드 이름으로 쿼리 생성

쿼리 메소드 기능 3가지

→ 각각의 기능 설명을 회원의 이름과 나이로 조회하는 예제를 통해 알아 볼 것입니다.
메소드 이름으로 쿼리 생성
→ 메소드 이름을 분석해서 JPQL 쿼리 실행
1.
순수 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", Member.class) .setParameter("username", username) .setParameter("age", age).getResultList(); }
Java
복사
2.
스프링 데이터 JPA
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
Java
복사
스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행한다.
메소드 이름으로 JPA NamedQuery 호출
→ JPA의 NamedQuery를 호출할 수 있다.
1.
Entity@NamedQuery 를 정의
/* Entity Area */ @Entity @NamedQuery(name="Member.findByUsername", query="select m from Member m where m.username = :username" ) public class Member { ... }
Java
복사
2.
순수 JPA를 이용한 NamedQuery 호출
public class MemberRepository { public List<Member> findByUsername(String username) { ... List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class) .setParameter("username", username) .getResultList(); } }
Java
복사
3.
스프링 데이터 JPA를 이용한 NamedQuery 사용
@Query(name = "Member.findByUsername") List<Member> findByUsername(@Param("username") String username);
Java
복사
@Query 를 생략하고 메서드 이름만으로 NamedQuery 호출이 가능하다.
@Query 어노테이션을 사용해서 리파지토리 인터페이스에 쿼리 직접 정의
1.
스프링 데이터 JPA를 이용한 NamedQuery 호출
List<Member> findByUsername(@Param("username") String username);
Java
복사
참고: 스프링 데이터 JPA를 사용하면 실무에서 NamedQuery를 직접 등록해서 사용하는 일은 드물다. 대신 @Query 를 사용해서 리파지토리 메서드에 쿼리를 직접 정의한다.

@Query, 리포지토리 메소드에 쿼리 정의하기

메서드에 JPQL 쿼리 작성

@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);
Java
복사
@Query 어노테이션을 사용한다.
실행할 메서드에 정적 쿼리를 직접 작성하기에 익명 NamedQuery라 할 수 있다.
JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있다.
참고: 실무에서는 쿼리 메서드기능을 이용하여 기능 구현하는건 파라미터가 증가할수록 메서드명이 복잡해지기 때문에 @Query 기능을 자주 사용하게 된다.

@Query, 값, DTO조회하기

단순히 값 하나를 조회

@Query("select m.username from Member m") List<String> findUsernameList();
Java
복사

DTO로 직접 조회

@Query("select new study.datajpa.repository.MemberDto(m.id, m.username, t.name)" + " from Member m join m.team t") List<MemberDto> findMemberDto();
Java
복사
DTO에 직접 매핑 해주려면 new 명령어를 사용해야 하고, 패키지경로를 모두 작성해줘야 한다.
DTO에 생성자 형식, 순서에 맞게 작성이 되어야 한다.

파라미터 바인딩

→ 파라미터 바인딩은 위치기반 바인딩이름 기반 바인딩 두가지가 있다.
select m from Member m where m.username = ?0 //위치 기반 select m from Member m where m.username = :name //이름 기반
Java
복사

이름기반 바인딩을 사용하자

→ 위치 기반 파라미터 바인딩은 해당 위치에 어떤 파라미터가 매핑되는지 가독성이 떨어지고, 추후 기능변경으로 인해 중간에 다른 파라미터가 추가될 경우 에러가 날 확률이 높다.
→ 코드 가독성과 유지보수를 위해서 이름 기반 파라미터 바인딩을 사용하도록 하자.

컬렉션 파라미터 바인딩

→ Collection 타입으로 in절 지원이 가능하다.
@Query("select m from Member m where m.username in :names") List<Member> findByNames(@Param("names") List<String> names);
Java
복사

반환 타입

List<Member> findByUsername(String name); //컬렉션 Member findByUsername(String name); //단건 Optional<Member> findByUsername(String name); //단건 Optional
Java
복사
조회 결과가 없을 경우
1.
컬렉션 → 빈 컬렉션 반환
2.
단건 → null 반환
결과가 2건 이상일 경우
1.
단건 → javax.persistence.NonUniqueResultException 예외 발생

순수 JPA 페이징과 정렬

Condition

검색 조건: 나이가 10살 이상
정렬 조건: 이름으로 내림차순
페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

Repository

Code

TestCase

Code

스프링 데이터 JPA 페이징과 정렬

→ 스프링 데이터 JPA에서는 페이징과 정렬을 공통화를 해놨다.

org.springframework.data.domain.Sort :정렬기능
org.springframework.data.domain.Pageable : 페이징 기능(내부에 Sort 포함)

특별한 반환 타입

org.springframework.data.domain.Page : 추가 count쿼리 결과를 포함하는 페이징
org.springframework.data.domain.Slice :추가 count 쿼리 없이 다음 페이지만 확인 가능( 내부적으로 limit + 1 조회)
List(자바 컬렉션): 추가 count쿼리 없이 결과만 반환

페이징과 정렬 사용 예제

Page<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 Slice<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 안 함 List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 안 함 List<Member> findByUsername(String name, Sort sort);
Java
복사

Example: 다음 조건으로 페이징과 정렬을 사용해보자.

Condition

검색 조건: 나이가 10살 이상
정렬 조건: 이름으로 내림차순
페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

Repository

Code

TestCase

Code
참고: count 쿼리는 분리할 수 있다. → 복잡한 쿼리에서 count쿼리는 굳이 left join을 할 필요가 없기에 이런 복잡한 sql을 사용하는 경우에는 분리해서 사용한다.
스프링 데이터 JPA를 이용한 count 쿼리 분리
Code
참고: 페이지를 유지하며 엔티티를 DTO로 변환할 수 있다. → 실무에서 엔티티를 그대로 반환하는 것은 위험하다. 그렇기 때문에 엔티티를 DTO로 변환하여 반환해야 한다.
Code

벌크성 수정 쿼리

→ 벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 사용해야 합니다.
사용하지 않으면 QueryExcutionRequestException 예외가 발생한다.
→ 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트 초기화: @Modifying(clearAutomatically = true)
이 옵션 없이 회원을 다시 조회하면 영속성 컨텍스트에 과거 값이 캐싱되어 있기 때문에, 정합성이 깨지게 된다. 강제로 영속성 컨텍스트를 초기화를 꼭 해주던가 해당 어노테이션을 설정해주도록 하자.
→ 비즈니스 로직에서 벌크성 수정 쿼리만 동작하고 트랜잭션 종료하는게 사실 제일 깔끔하다.

스프링 데이터 JPA를 사용한 벌크성 수정 쿼리

1.
Repository
Code
2.
TestCase
Code

@EntityGraph

→ 연관된 엔티티들을 SQL 한번에 조회하는 방법

Case 1: Member를 조회하면서 Team 도 같이 조회해 보자.

현재 memberteam 은 지연로딩 관계이다.
Code

→ 스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 사용하여 JPQL없이 페치 조인을 사용할 수 있다.

Code
참고: NamedEntityGraph로도 EntityGraph를 사용할 수 있다.
/* Entity */ @NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team")) @Entity public class Member {} ... /* Repository */ @EntityGraph("Member.all") @Query("select m from Member m") List<Member> findMemberEntityGraph();
Java
복사

JPA Hint& Lock

1. QueryHint

→ JPA 쿼리 힌트(SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)

Repository

Code

TestCase

Code
정리: 굳이 QueryHint까지 써야할 정도의 코드 최적화와 성능튜닝이 요구되는 경우는 거의 없다. 성능이 극한으로 요구된다해도 복잡한 쿼리에서 쿼리 자체의 문제로 느려지는 경우가 대다수이기 때문에 QueryHint를 하나하나 적어주기보다는 조회 쿼리를 수정하는 것이 맞다. 그리고, QueryHint는 운영을 하면서 조금씩 여유가 될 때 정 필요하면 넣어주는 정도로도 충분하다.

2. Lock

org.springframework.data.jpa.repository.Lock 어노테이션을 사용한다.

@Lock(LockModeType.PESSIMISTIC_WRITE) List<Member> findByUsername(String name);
Java
복사
참고: 잠금(Lock)의 종류