Search
Duplicate
📒

[스프링 JPA] 03-2. 쿼리 메소드 기능2 ⭐

상태
완료
수업
JPA
주제
JPA
연관 노트
3 more properties
참고

반환 타입

NOTE
스프링 데이터 JPA유연한 반환 타입을 지원한다.
List<Member> findByUsername(String name); //컬렉션 Member findByUsername(String name); //단건 Optional<Member> findByUsername(String name); //단건 Optional
Java
복사
무려 3가지 방식을 지원한다!

조회 결과가 없는경우

public void returnType(){ Member m1 = new Member("AAA", 10); Member m2 = new Member("BBB", 20); memberRepository.save(m1); memberRepository.save(m2); List<Member> members = memberRepository.findListByUserName("AAA"); Member member = memberRepository.findMemberByUserName("AAA"); Optional<Member> member2 = memberRepository.findOptionalByUserName("AAA"); List<Member> empty = memberRepository.findListByUserName("AAA111"); System.out.println("empty.size() = " + empty.size()); Member findMember = memberRepository.findMemberByUserName("AAA111"); System.out.println("findMember = " + findMember); Optional<Member> findOptionalMember = memberRepository.findOptionalByUserName("AAA111"); System.out.println("findOptionalMember = " + findOptionalMember); }
Java
복사
empty.size() = 0 findMember = null findOptionalMember = Optional.empty
컬렉션
빈 컬렉션 반환
단건조회
null 반환
스프링 데이터 JPA가 아닌 순수 JPA는 예외가 발생한다

조회 결과가 많을 경우

단건 조회
예외발생

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

NOTE

페이징과 정렬 파라미터

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 쿼리 없이 결과만 반환

페이징과 정렬 사용 예제

NOTE
Page<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 Slice<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 X List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 X List<Member> findByUsername(String name, Sort sort);
Java
복사
Page타입만 count쿼리가 자동으로 사용된다.
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")); // when Page<Member> page = memberRepository.findByAge(age, pageRequest); Page<MemberDto> toMap = page.map(member -> new MemberDto(member.getId(), member.getUserName(), null)); // then List<Member> content = page.getContent(); assertThat(content.size()).isEqualTo(3); // 1페이지당 개수 assertThat(page.getTotalElements()).isEqualTo(5); // 총 엔티티개수 assertThat(page.getNumber()).isEqualTo(0); // 페이지(0부터 시작) assertThat(page.getTotalPages()).isEqualTo(2); // 페이지가 2페이지까지 있다. assertThat(page.isFirst()).isTrue(); // 처음 페이지인가? assertThat(page.hasNext()).isTrue(); // 다음 페이지가 있는가? }
Java
복사

벌크성 쿼리

NOTE
벌크성 쿼리란? → 대량의 데이터들을 쿼리를 통해 수정한다는 것이다.
벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 사용해야 한다.
사용하지 않으면 QueryExecutionRequestException예외가 발생한다.
벌크성 쿼리를 실행하고 나서 영속성 컨텍스트를 초기화해줘야한다!
Modifying(clearAutomatically = true)
이 옵션을 사용하지 않으면, 회원을 다시 조회했을때 영속성 컨텍스트와 DB의 데이터가 불일치해서 정합성이 깨지게된다!

벌크성 쿼리 사용 예제

NOTE
@Modifying(clearAutomatically = true) @Query("update Member m set m.age = m.age + 1 where m.age >= :age") int bulkAgePlus(@Param("age") int age);
Java
복사
특정 나이 이상인 사람들의 나이를 +1 해준다.
public void bulkUpdate() { memberRepository.save(new Member("member1", 10)); memberRepository.save(new Member("member2", 19)); memberRepository.save(new Member("member3", 20)); memberRepository.save(new Member("member4", 21)); memberRepository.save(new Member("member5", 40)); int resultCount = memberRepository.bulkAgePlus(20); em.flush(); em.clear(); List<Member> result = memberRepository.findByUsername("member5"); Member member = result.get(0); System.out.println("member = " + member); assertThat(resultCount).isEqualTo(3); }
Java
복사
20살 이상인 사람들의 나이가 모두 +1됨

@EntityGraph

NOTE
연관된 엔티티들을 SQL 한번에 조회하는 방법
member → team은 지연로딩(LAZY) 관계이다.
따라서 다음과 같이 team의 데이터를 조회할 떄마다 쿼리가 실행한다. (N+1 문제 발생!)
public void findMemberLazy(){ //given Team teamA = new Team("teamA"); Team teamB = new Team("teamB"); teamRepository.save(teamA); teamRepository.save(teamB); Member member1 = new Member("member1", 10, teamA); Member member2 = new Member("member2", 10, teamB); memberRepository.save(member1); memberRepository.save(member2); em.flush(); em.clear(); //when // select Member N + 1 List<Member> members = memberRepository.findAll(); for (Member member : members) { System.out.println("member = " + member.getUserName()); System.out.println("member.team = " + member.getTeam().getName()); // 문제되는 쿼리 } }
Java
복사
해당 쿼리에서, Team을 조회하기 위해 쿼리가 계속 실행됨

JPQL fetch join

NOTE
//공통 메서드 오버라이드 @Override @EntityGraph(attributePaths = {"team"}) List<Member> findAll(); //JPQL + 엔티티 그래프 @EntityGraph(attributePaths = {"team"}) @Query("select m from Member m") List<Member> findMemberEntityGraph(); //메서드 이름으로 쿼리에서 특히 편리하다. @EntityGraph(attributePaths = {"team"}) List<Member> findEntityGraphByUsername(@Param("username") String username);
Java
복사
스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 사용하여 JPQL없이 fetch조인을 사용할 수 있다!
JPQL + 엔티티 그래프도 사용가능

JPA Hint& Lock

JPA Hint

NOTE
JPA 쿼리 힌트 (SQL 힌트가 아닌 JPA 구현체에게 제공하는 힌트)
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true")) Member findReadOnlyByUsername(String username);
Java
복사
쿼리를 읽기 전용으로 조회한다고 힌트를 주고 있다.
[ 참고]
QueryHint까지 서야할 정도의 코드 최적화와 성능 튜닝이 요구되는 경우는 거의 없다!
성능이 극한으로 요구된다해도 복잡한 쿼리에서의 문제로 발생하는게 대다수 이기 때문
때문에 하나하나 QueryHint를 적용하기 보다는, 성능 테스트를 돌리면서 판단할 것!

Lock