참고
반환 타입
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를 적용하기 보다는, 성능 테스트를 돌리면서 판단할 것!