DevKim

[Data JPA] 쿼리 메소드 기능 (@Query, 파라미터 바인딩..등) 본문

JPA

[Data JPA] 쿼리 메소드 기능 (@Query, 파라미터 바인딩..등)

on_doing 2021. 7. 1. 14:18
728x90

실습을 위해 간단한 일대다 양방향 매핑을 걸어준 Member 와 Team 엔티티를 생성하였고

JPA Repository와 DataJPA Repository를 각각 작성하여 각각의 테스트 코드를 작성해보았다.

 

JPA에서는 엔티티매니저 불러와서 persist해주고 조회시 쿼리 만들어서 조회하고..

Data JPA는 인터페이스 하나로 마무리된다는게 아직도 신기한 일이다.

 

다른건 생략하고, 

한가지 다시 짚고 넘어가야 할 점이 있었다.

 

여기서 equals를 따로 override 안했기 때문에, 현재 isEqualTo에는 기본적으로 == 비교를 하게 되어있다.

이때 findMember와 member 가 같다고 나오는데,

이는 JPA 특성상 같은 트렌젝션 안에서는 영속성 컨텍스트의 동일성을 보장하기 때문이다.

@SpringBootTest
@Transactional
@Rollback(false)
class MemberRepositoryTest {

    @Autowired
    MemberRepository memberRepository;

    @Test
    public void testMember() throws Exception{
        //given
        Member member = new Member("memberA");   
        ...

        //when
        Member findMember = memberRepository.findById(savedMember.getId()).orElseThrow(
                ()-> new IllegalArgumentException("해당 아이디에 맞는 데이터가 없습니다.")
        );

        //then
        
        ...

        /*
        JPA 엔티티 동일성 보장
         */
        assertThat(findMember).isEqualTo(member);
    }
}

[ 주요 메서드 ]

  • save,delete,findById
  • getOne(ID) : 엔티티를 프록시 객체로 조회한다.
  • findAll (..) : 모든 엔티티를 조회. sort 나 Pageable 조건으로 파라미터를 제공할 수 있다.

getOne ()                                                                         findById () 

대상 엔터티에 대한 지연로드 된 참조 주어진 ID에 대한 엔티티를 실제로 로드
객체의 속성에 액세스 할 필요가없는 경우에만 유용 모든 속성에 액세스 할 수 있도록 객체가 로드 됨
액세스 호출시 실제 오브젝트가 존재하지 않으면 EntityNotFoundException을 던짐. 주어진 ID에 해당하는 실제 객체가 존재하지 않으면 null을 반환
더 나은 성능 데이터베이스에 대한 추가 왕복이 필요

[ 쿼리 메서드 ]

쿼리 메소드 기능에는 엄청 많은 기능들이 있다

  • 메소드 이름으로 쿼리 생성
  • @Query
  • 파라미터 바인딩
  • 반환 타입
  • 페이징과 정렬
  • 벌크성 수정 쿼리
  • @EntityGraph

1. 메소드 이름으로 쿼리 생성

간단한 쿼리 날릴 때 유용하고, 쿼리가 복잡해지면 점점 길어지므로 @Query로 풀어내는 것이 좋다고한다.

 

아래의 Reference를 참고하면 다양한 기능들이 존재하는 것을 확인할 수 있다.

Spring Data JPA - Reference Documentation

 

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

 List<Member> findByUsernameAndAgeGreaterThan(String username,int age);

테스트 코드

...
@Test
    public void test() throws Exception{
        //given
        Member member1 = new Member("memberA",10);
        Member member2 = new Member("memberA",20);
        memberRepository.save(member1);
        memberRepository.save(member2);

        //when
        List<Member> memberList = memberRepository.findByUsernameAndAgeGreaterThan("memberA", 10);

        //then
        assertThat(memberList.get(0).getUsername()).isEqualTo("memberA");
        assertThat(memberList.get(0).getAge()).isEqualTo(20);
        assertThat(memberList.size()).isEqualTo(1);

    }

2. @Query

어노테이션을 사용해서 repository interface에 쿼리 직접 정의

이 방법이 실무에서 가장 많이 쓰이고, 복잡한 쿼리들을 이렇게 풀어서 사용한다고 한다.

 

-> 어플리케이션 실행 시점에 문법 오류를 발견할 수 있는 아주 큰 장점이있다

 

Interface

@Query("select m from Member m where m.username=:username and m.age=:age")
List<Member> findMember(@Param("username") String username,@Param("age") int age);

Test

@Test
public void testQuery() throws Exception{
  //given
  Member member1 = new Member("memberA",10);
  Member member2 = new Member("memberA",20);
  memberRepository.save(member1);
  memberRepository.save(member2);

  //when
  List<Member> result = memberRepository.findMember("mamberA", 10);

  //then
  assertThat(result.get(0)).isEqualTo(member1);
}

* 실무에서 많이 사용하는 기능 *

 

지금까지는 단순 엔티티가 아닌 단순한 값이나 DTO를 조회하는 방법

 

(1) 값 조회

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

(2) DTO 조회

 

DTO를 조회할 때는, 패키지명부터 클래스명까지 모두 적어주어야하는데,

이건 나중에 Querydsl 공부한 후에 개선해봐야겠다.

@Query("select new study.datajpa.models.MemberDto(m.id,m.username,t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();

단순 출력 Test

@Test
public void QueryFindDto() throws Exception{
    //given
    Team team = new Team("TeamA");
    teamRepository.save(team);

    Member member1 = new Member("memberA",10);
    member1.changeTeam(team);
    Member member2 = new Member("memberA",20);
    member2.changeTeam(team);
    memberRepository.save(member1);
    memberRepository.save(member2);

    //when
    List<MemberDto> memberDto = memberRepository.findMemberDto();

    //then
    for (MemberDto dto : memberDto) {
    System.out.println("dto = " + dto);

}

}

[ 파라미터 바인딩 ]

@Param


[ 컬렉션 파라미터 바인딩 ]

 

IN 절로 여러개를 받아오고 싶을 때

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

단순 출력 Test

@Test
public void CollectionBind() throws Exception{

    //given
    Member member1 = new Member("memberA",10);
    Member member2 = new Member("memberB",20);
    memberRepository.save(member1);
    memberRepository.save(member2);

    //when
    List<Member> usernameList=memberRepository.findByNames(Arrays.asList("memberA","memberB"));

    //then
    for (Member member : usernameList)
    {
    System.out.println("member = " + member);
    }
}

[ 반환타입 ]

Data JPA는 유연한 반환 타입을 지원한다.

 

find와 By 사이에는 아무거나 써도 상관없기에 아무거나..

List<Member> findListByUsername(String username); //컬렉션

Member findBlahByUsername(String username); //단건건

Optional<Member> findOpByUsername(String username); //Optional 단건

 

** 주의 **

만약 컬렉션으로 반환할 때,

1. 결과가 0개 (없으면) - 빈 컬렉션 반환

 

단건 조회시,

1. 결과가 0개 - null반환 (Optional로 받기 추천)

2. 결과가 2건 이상 - Exception 발생

728x90
Comments