메소드 이름으로 쿼리 생성
쿼리 메소드 기능 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 도 같이 조회해 보자.
•
현재 member → team 은 지연로딩 관계이다.
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)의 종류