메소드에 JPQL 쿼리 작성
@org.springframework.data.jpa.repository.Query 어노테이션 사용
실행할 메소드에 정적 쿼리를 직접 작성하므로 이름없는 Named 쿼리라고 할 수 있음
JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음!
참고: 실무에서는 메소드 이름으로 쿼리 생성 기능은 파라미터가 증가하면 메소드 이름이 매우 지저분해진다. 따라서 @Query기능을 자주 사용하게 된다.
@Query("select m.username from Member m")
List<String> findUsernameList();
@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와 사용방식이 동일하다)
package study.datajpa.repository;
import lombok.Data;
@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;
}
}
import org.springframework.data.repository.query.Param
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :name")
Member findMembers(@Param("name") String username);
}
참고: 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하자
Collection
타입으로 in절 지원@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
List<Member> findListByUsername(String name); //컬렉션
Member findMemberByUsername(String name); //단건
Optional<Member> findOptionalByUsername(String name); //단건 Optional
참고: 단건으로 지정한 메소드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingleResult()메소드를 호출한다. 이 메소드를 호출했을 때 조회 결과가 없으면 javax.persistence.NoResultException예외가 발생하는데 개발자 입장에서 다루기가 상당히 불편하다. 스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 null을 반환한다.
public List<Member> findByPage(int age, int offset, int limit) {
return em.createQuery("select m from Member m where m.age = :age
order by m.username desc").setParameter("age", age)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
public long totalCount(int age) {
return em.createQuery("select count(m) from Member m where m.age = :age",Long.class)
.setParameter("age", age)
.getSingleResult();
}
Page<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
Slice<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
안함
List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
안함
List<Member> findByUsername(String name, Sort sort);
주의: Page는 1부터 시작이 아니라 0부터 시작이다.
Page<Member> page = memberRepository.findByAge(10, pageRequest);
Page<MemberDto> dtoPage = page.map(m -> new MemberDto());
//공통 메서드 오버라이드
@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> findByUsername(String username)
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value =
"true"))
Member findReadOnlyByUsername(String username);
@Test
public void queryHint() throws Exception {
//given
memberRepository.save(new Member("member1", 10));
em.flush();
em.clear();
//when
Member member = memberRepository.findReadOnlyByUsername("member1");
member.setUsername("member2");
em.flush(); //Update Query 실행X
}
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);