BackEnd/JPA

[JPA] 쿼리 메소드 기능 - 2

샤아이인 2022. 5. 4.

내가 공부한 것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼 겸 상세히 기록하고 얕은 부분들은 가볍게 포스팅하겠습니다.

 

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이 통일시켜서 동일한 예외를 터트려 주는 것이다.

 

더 다양한 반환타입은 다음 레퍼런스를 참고하자!

 

Spring Data JPA - Reference Documentation

Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

원래 단건 조회시 스프링 데이터 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

댓글