Programming/Spring

Spring Data JPA - 쿼리 메소드 (메소드 이름으로 쿼리 생성, @Query)

잇(IT) 2023. 7. 24. 22:04

- 쿼리 메소드 기능 3가지
1. 메소드 이름으로 쿼리 생성

2. 메소드 이름으로 JPA NamedQuery 호출

3. @Query 어노테이션을 사용해서 Repository 인터페이스에 쿼리 직접 정의


- 메소드 이름으로 쿼리 생성

- 순수 JPA Repository

public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
 	return em.createQuery("select m from Member m where m.username = :username
and m.age > :age")
 		.setParameter("username", username)
 		.setParameter("age", age)
 		.getResultList();
}

 

- 스프링 데이터 JPA

public interface MemberRepository extends JpaRepository<Member, Long> {
 	List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}

- 스프링 데이터 JPA를 이용하면 메소드 이름을 분석해서 JPQL을 생성하고 실행한다.

 

- 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능

1. 조회 : find...By / read...By / query...By / get...By

1.1. ex) findHelloBy처럼 ...에 식별하기 위한 내용(설명)이 들어가도 된다.

2. COUNT : count...By - 반환타입 long

3. EXISTS : exists...By - 반환타입 boolean

4. 삭제 : delete...By, remove...By - 반환타입 long

 

- 이 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 한다. 그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다.

- 이렇게 애플리케이션 로딩 시점에 오류를 인지할 수 있는 것이 스프링 데이터 JPA의 매우 큰 장점이다.


- @Query, 리포지토리 메소드에 쿼리 정의하기

 

- MemberRepository에 작성

@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);

- @Query에 해당하는 부분에 JPQL을 작성함으로서 메서드명과 별개로 쿼리를 작성 할 수 있다.

- @Param을 이용하여 파라미터로 값을 넣을 수 있다.

 

@Test
    public void testQUery() {
        Member m1 = new Member("AAA", 10);
        Member m2 = new Member("AAA", 20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        List<Member> result = memberRepository.findUser("AAA", 10);
        assertThat(result.get(0)).isEqualTo(m1);
    }

- @Query는 쿼리 조건이 여러개이고, 메서드 이름이 지저분해질 경우 주로 사용한다.


- @Query, 값, DTO 조회하기

 

- 단순히 값 하나를 조회

@Query("select m.username from Member m")
    List<String> findUserNameList();

- JPA 값 타입(@Embedded)도 이 방식으로 조회할 수 있다.

 

- DTO로 직접 조회

@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가 필요하다. (JPA와 사용방식이 동일하다.)

 

- MemberDto

@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;
    }
}

 

- Test 코드

@Test
    public void findMemberDto() {

        Team team = new Team("teamA");
        teamRepository.save(team);

        Member m1 = new Member("AAA", 10);
        m1.setTeam(team);
        memberRepository.save(m1);

        Member m2 = new Member("BBB", 20);
        m2.setTeam(team);
        memberRepository.save(m2);

        List<MemberDto> memberDto = memberRepository.findMemberDto();
        for (MemberDto dto : memberDto) {
            System.out.println("dto = " + dto);
        }
    }

- DTO를 직접 조회하는 방식으로 엔티티가 외부에 노출되지 않고 원하는 데이터만 골라서 데이터를 전송할 수 있다.


- 파라미터 바인딩

1. 위치 기반

2. 이름 기반 (권장)

select m from Member m where m.username = :name //이름 기반

 

- 파라미터 바인딩

@Query("select m from Member m where m.username in :names")
    List<Member> findByNames(@Param("names") List<String> names);

 

- Test 코드

@Test
    public void findByNames() {
        Member m1 = new Member("AAA", 10);
        Member m2 = new Member("BBB", 20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        List<Member> result = memberRepository.findByNames(Arrays.asList("AAA", "BBB"));
        for (Member member : result) {
            System.out.println("member = " + member);
        }
    }

- (Arrays.asList를 통해 배열을 List로 변환할 수 있다.)

- @Param과, In 쿼리, List를 이용해서 원하는 조건의 데이터를 쿼리 한번에 조회할 수 있다.


- 반환 타입

- 스프링 데이터 JPA는 유연한 반환 타입을 지원한다.

List<Member> findByUsername(String name); //컬렉션
Member findByUsername(String name); //단건
Optional<Member> findByUsername(String name); //단건 Optional

 

- 조회 결과가 많거나 없으면?

1. 컬렉션

1.1. 결과 없음: 빈 컬렉션 반환

2. 단건 조회

2.1. 결과 없음: null 반환

2.2. 결과가 2건 이상: javax.persistence.NonUniqueResultException 예외 발생

@Test
    public void returnType() {
        Member m1 = new Member("AAA", 10);
        Member m2 = new Member("BBB", 20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        List<Member> aaa = memberRepository.findListByUsername("AAA");
        
        Member bbb = memberRepository.findMemberByUsername("AAA");
        System.out.println("bbb = " + bbb);
        
        Optional<Member> ccc = memberRepository.findOptionalByUsername("AAA");
        System.out.println("ccc.get() = " + ccc.get());

        List<Member> result = memberRepository.findListByUsername("ASDF");
        System.out.println("result.size() = " + result.size());

        Member findMember = memberRepository.findMemberByUsername("ASDF");
        System.out.println("findMember = " + findMember);

    }
}

- 컬렉션 조회할 때 이상한 값을 넣으면 조회 데이터가 없을 수 있다. -> Null로 반환되는 것이 아니라 빈 결과값을 보낸다. (if != null 이런 것 사용하지 않도록 주의해야 한다.)

- 단건 조회는 Null로 반환된다.

- 단건에서 있을수도 있고 없을수도 있으면 Optional을 쓰는 것이 좋다. Null 예외를 Optional에서 처리해주기 때문이다.

- 하지만, 단건 조회에서 여러 데이터가 조회될 경우 예외가 발생한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 : 인프런 - 김영한(실전! 스프링 데이터 JPA)

 

 

728x90