내가 공부한 것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼 겸 상세히 기록하고 얕은 부분들은 가볍게 포스팅하겠습니다.
4. @Query, 값, DTO 조회하기
이전시간에는 Entity를 조회하는 방식에 대하여 알아보았다.
이번시간에는 값을 찾아오는 법을 살펴보자.
1) 단순 값 조회하기
예를 들어 사용자의 이름 목록을 받아오고 싶다면 어떻게 해야할까?
이전과 동일하게 @Query를 사용하면 된다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m.username from Member m")
List<String> findUsernameList();
}
테스트 코드는 다음과 같다.
@Test
public void findUsernameTest() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("BBB", 20);
memberRepository.save(m1);
memberRepository.save(m2);
List<String> usernameList = memberRepository.findUsernameList();
Assertions.assertThat(usernameList).contains("AAA", "BBB");
}
테스트가 정상적으로 실행됨을 알 수 있다.
2) DTO로 조회하기
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
}
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;
}
}
이에 대한 테스트 코드는 다음과 같다.
원래였다면 assert 문을 작성해 검증하겠지만, 공부하는 과정이니 결과를 출력해보기 위해 print문을 사용하였다.
@Test
public void findDtoTest() {
Team team = new Team("teamA");
teamRepository.save(team);
Member m1 = new Member("AAA", 10, team);
memberRepository.save(m1);
List<MemberDto> memberDtos = memberRepository.findMemberDto();
for (MemberDto memberDto : memberDtos) {
System.out.println("memberDto = " + memberDto);
}
}
출력되는 결과는 다음과 같다.
5. 파라미터 바인딩
1) 파라미터 바인딩
파라미터 바인딩은 위치 기반과 이름 기반으로 하는 방식 2가지가 있다.
select m from Member m where m.username = ?0 //위치 기반
select m from Member m where m.username = :name //이름 기반
이름 기반 방식을 사용하기를 권장한다.
위치 기반은 파라미터의 순서가 달라지면 인덱스 번호 까지 변경해야 하는 문제가 발생한다.
가독성 도 사실 이름기반이 훨씩 좋다.
2) 컬렉션 파라미터 바인딩
컬렉션 같은 경우 in쿼리를 자동으로 만들어 준다. 파라미터로 Collection을 넘기기만 하면 된다.
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
테스트 코드는 다음과 같다.
@Test
public void findNamesTest() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("BBB", 20);
memberRepository.save(m1);
memberRepository.save(m2);
List<Member> members = memberRepository.findByNames(Arrays.asList("AAA", "BBB"));
Assertions.assertThat(members).contains(m1, m2);
}
테스트가 정상적으로 통과된다.
실행시 나오는 쿼리는 다음과 같다. in절 안에 Collection의 원소들이 전달되었다!
select member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from member member0_
where member0_.username in ('AAA' , 'BBB');
6. 반환 타입
Spring Data JPA는 유연한 반환 타입을 지원한다.
List<Member> findByUsername(String name); //컬렉션
Member findByUsername(String name); //단건
Optional<Member> findByUsername(String name); //단건 Optional
만약 조회시 해당 데이터가 없는 경우에는 다음과 같다.
- 컬렉션 조회시 -> 비어있는 컬렉션 반환, null이 아니다!
- 단건 조회시 -> null 반환
만약 단건 조회시 결과가 2건 이상이 나온다면?
- javax.persistence.NonUniqueResultException 예외가 발생한다.
한가지 재미있는 점은 JPA 에서는 NonUniqueResultException 이 발생했다.
이걸 Spring Data JPA가 springframework의 exception인 IncorrectResultSizeDateAccessException 으로 바꿔서 터트린다.
원래 DB마다 발생할 수 있는 예외가 다를 수 있는데, 이걸 Spring이 통일시켜서 동일한 예외를 터트려 주는 것이다.
더 다양한 반환타입은 다음 레퍼런스를 참고하자!
원래 단건 조회시 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingleResult() 메서드를 호출하게 된다.
JPA는 단건조회, 즉 getSingleResult()를 호출할때 결과가 없다면 javax.persistence.NoResultException 이 발생하게 된다.
이걸 매번 try-catch로 개발자가 직접 감싸주기도 불편하다.
따라서 Spring Data JPA 는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 null 을 반환한다.
'BackEnd > JPA' 카테고리의 다른 글
[JPA] 쿼리 메소드 기능 - 4 (0) | 2022.05.04 |
---|---|
[JPA] 쿼리 메소드 기능 - 3 (0) | 2022.05.04 |
[JPA] 쿼리 메소드 기능 - 1 (0) | 2022.05.03 |
[JPA] 공통 인터페이스 기능 (0) | 2022.05.03 |
[JPA] 예제 도메인 모델 (0) | 2022.05.02 |
댓글