Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
8. 프록시와 연관관계 관리
9. 값 타입
9.3~5 값 타입과 불변 객체, 값 비교, 값 타입 컬렉션
10. 객체지향 쿼리 언어
11. 웹 애플리케이션 제작
11.1 프로젝트 환경설정
11.2 도메인 모델과 테이블 설계
11.3 애플리케이션 구현
12. 스프링 데이터 JPA
12.4 쿼리 메소드 기능
- 쿼리 메소드 기능은 스프링 데이터 JPA가 제공하는 마법 같은 기능
- 대표적으로 메소드 이름만으로 쿼리를 생성하는 기능
- 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능은 크게 3가지가 존재
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
- @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의
12.4.1 메소드 이름으로 쿼리 생성
- 이메일과 이름으로 회원을 조회하려면 다음과 같은 메소드를 정의
//레포지토리 인터페이스 public interface MemberRepository extends Repository<Member, Long> { List<Member> findByEmailAndName(String email, String name); } //실행된 JPQL select m from Member m where m.email = ?1 and m.name = ?2- 스프링 데이터 쿼리 생성 관련 주소 : spring-data/jpa/docs Query Creation
스프링 데이터 JPA 쿼리 생성 기능
키워드 예 JPQL 예 Distinct findDistinctByLastnameAndFirstnameselect distinct … where x.lastname = ?1 and x.firstname = ?2And findByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2Or findByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2Is, Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1Between findByStartDateBetween… where x.startDate between ?1 and ?2LessThan findByAgeLessThan… where x.age < ?1LessThanEqual findByAgeLessThanEqual… where x.age <= ?1GreaterThan findByAgeGreaterThan… where x.age > ?1GreaterThanEqual findByAgeGreaterThanEqual… where x.age >= ?1After findByStartDateAfter… where x.startDate > ?1Before findByStartDateBefore… where x.startDate < ?1IsNull, Null findByAge(Is)Null… where x.age is nullIsNotNull, NotNull findByAge(Is)NotNull… where x.age not nullLike findByFirstnameLike… where x.firstname like ?1NotLike findByFirstnameNotLike… where x.firstname not like ?1StartingWith findByFirstnameStartingWith… where x.firstname like ?1(parameter bound with appended%)EndingWith findByFirstnameEndingWith… where x.firstname like ?1(parameter bound with prepended%)Containing findByFirstnameContaining… where x.firstname like ?1(parameter bound wrapped in%)OrderBy findByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname descNot findByLastnameNot… where x.lastname <> ?1In findByAgeIn(Collection<Age> ages)… where x.age in ?1NotIn findByAgeNotIn(Collection<Age> ages)… where x.age not in ?1True findByActiveTrue()… where x.active = trueFalse findByActiveFalse()… where x.active = falseIgnoreCase findByFirstnameIgnoreCase… where UPPER(x.firstname) = UPPER(?1)- 엔티티의 필드명이 변경되면 인터페이스에 정의한 메소드 이름 변경 필수
- 변경하지 않으면 애플리케이션 시작하는 시점에 오류 발생
12.4.2 JPA NamedQuery
- JPA Named 쿼리는 이름 그대로 쿼리에 이름을 부여해서 사용하는 방법
기존 Named 쿼리 호출 (10.2.15절 참고)
//Entity @Entity @NamedQuery( name="Member.findByUsername", query="select m from Member m where m.username = :username") public class Member { ... } //Repository public class MemberRepository { ... List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class) .setParameter("username", "회원1") .getResultList(); }스프링 데이터 JPA
public interface MemberRepository extends JpaRepository<Member, Long> { List<Member> findByUsername(@Param("username") String username); }- 스프링 데이터 JPA는 "도메인 클래스 + .(점) + 메소드 이름" 으로 Named쿼리를 찾아 실행
- 만약 Named쿼리가 없으면 메소드 이름으로 쿼리 생성 전략 사용
- Param은 이름 기반 파라미터를 바인딩할 때 사용하는 어노테이션
12.4.3 @Query, 리포지토리 메소드에 쿼리 정의
- 리포지토리 메소드에 직접 쿼리를 정의하려면 @Query 어노테이션을 사용
- JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류 발견이 가능
public interface MemberRepository extends JpaRepository<Member, Long> { @Query("select m from Member m where m.username = ?1") Member findByUsername(String username); }
- 네이티브 SQL을 사용하려면 @Query 어노테이션에 nativeQuery = true를 설정
- 스프링 데이터 JPA의 파라미터 바인딩의 경우 JPQL은 위치 기반 파라미터를 1부터 시작 네이티브 SQL은 0부터 시작
public interface MemberRepository extends JpaRepository<Member, Long> { @Query(value = "SELECT * FROM MEMBER WHERE USERNAME = ?0", nativeQuery = true) Member findByUsername(String username); }
12.4.4 파라미터 바인딩
- 스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원
select m from Member m where m.username = ?1 //위치 기반 select m from Member m where m.username = :name //이름 기반
- 기본값은 위치 기반이며 파라미터 순서로 바인딩
- 이름 기반 파라미터 바인딩을 사용하려면 @Param 어노테이션을 사용
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 findByUsername(@Param("name") String username); }
- 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용
12.4.5 벌크성 수정 쿼리
- JPA로 작성한 벌크성 수정 쿼리
int bulkPriceUp(String stockAmount) { ... String qlString = "update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount"; int resultCount = em.createQuery(qlString) .setParameter("stockAmount", stockAmount) .executeUpdate(); }
- 스프링 데이터 JPA를 사용한 벌크성 수정 쿼리
@Modifying @Query("update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount") int bulkPriceUp(@Param("stockAmount") String stockAmount);- 스프링 데이터 JPA에서 벌크성 수정, 삭제 쿼리는 Modifying 어노테이션을 사용
- 쿼리 후 영속성 컨텍스트를 초기화하고 싶으면
@Modifying(clearAutomatically = true)처럼 clearAutomatically 옵션을 true로 설정 (기본 값은 false)
12.4.6 반환 타입
- 스프링 데이터 JPA는 유연한 반환 타입을 지원
결과가 한 건 이상이면 컬렉션 인터페이스, 단건이면 반환 타입을 지정
List<Member> findByName(String name); //컬렉션 Member findByEmail(String email); //단건- 조회 결과가 없으면 컬렉션은 빈 컬렉션, 단건은 null을 반환
- 단건의 경우 2건 이상 조회되면
NonUniqueResultException예외가 발생- 단건으로 지정한 메소드를 호출하면 JPQL의
Query.getSingleResult()메소드를 호출
- 단건으로 지정한 메소드를 호출하면 JPQL의
12.4.7 페이징과 정렬
- 스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬을 사용 가능한 2가지 특별 파라미터 제공
org.springframework.data.domain.Sort: 정렬 기능
org.springframework.data.domain.Pagable: 페이징 기능(내부에 Sort 포함)
- 파라미터에 Pageable을 사용하면 반환 타입으로 List나 Page를 사용 가능
- 반환 타입이 Page면 페이징 기능을 제공하기 위해 전체 데이터 건수 조회 쿼리를 추가 호출
//count 쿼리 사용 (전체 데이터 건수 조회 쿼리) Page<Member> findByName(String name, Pageable pageable); //count 쿼리 사용 안 함 List<Member> findByName(String name, Pageable pageable); List<Member> findByName(String name, Sort sort);
- 아래의 조건으로 페이징과 정렬을 사용하는 예제
- 검색 조건: 이름이 김으로 시작하는 회원
- 정렬 조건: 이름으로 내림차순
- 페이징 조건: 첫 번쨰 페이지, 페이지당 보여줄 데이터는 10건
public interface MemberRepository extends Repository<Member, Long> { Page<Member> findByNameStartingWith(String name, Pageable pageable); }//페이징 조건과 정렬 조건 설정 PageRequest pageRequest = new PageRequest(0, 10, new Sort(Direction.DESC, "name")); Page<Member> result = memberRepository.findByNameStartingWith("김", pageRequest); List<Member> members = result.getContent(); //조회된 데이터 int totalPages = result.getTotalPages(); //전체 페이지 수 boolean hasNextPage = result.hasNextPage(); //다음 페이지 존재 여부- 두 번째 파라미터
Pageabled은 인터페이스, 따라서 구현체인PageRequest객체 사용
PageRequest생성자의 첫 번쨰 파라미터는 현재 페이지, 두 번째는 조회할 데이터 수를 입력 추가로 정렬 정보도 파라미터로 사용 가능
- 반환 타입인 Page 인터페이스가 제공하는 다양한 메소드들
public interface Page<T> extends Iterable<T> { int getNumber(); //현재 페이지 int getSize(); //페이지 크기 int getTotalPages(); //전체 페이지 수 int getNumberOfElements(); //현재 페이지에 나올 데이터 수 long getTotalElements(); //전체 데이터 수 boolean hasPreviousPage(); //이전 페이지 여부 boolean isFirstPage(); //현재 페이지가 첫 페이지 인지 여부 boolean hasNextPage(); //다음 페이지 여부 boolean isLastPage(); //현재 페이지가 마지막 페이지 인지 여부 Pagable nextPageable(); //다음 페이지 객체, 다음 페이지가 없으면 null Pagable previousPageable(); //다음 페이지 객체, 이전 페이지가 없으면 null List<T> getContent(); //조회된 데이터 boolean hasContent(); //조회된 데이터 존재 여부 Sort getSort(); //정렬 정보 }
12.4.8 힌트
- JPA 힌트를 사용하려면 @QueryHints 어노테이션을 사용
- 참고로 이것은 SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트
@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly" , value = "true") }, forCounting = true) Page<Member> findByName(String name, Pagable Pageable);- forCounting 속성은 반환 타입으로 Page 인터페이스를 적용하면 추가로 count 쿼리에도 쿼리 힌트를 적용할지를 설정하는 옵션 (기본값 true)
12.4.9 Lock
- 쿼리 시 락을 걸려면 @Lock 어노테이션을 사용 (16.1절 참고)
@Lock(LockModeType.PESSIMISTIC_WRITE) List<Member> findByName(String name);
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
| [자바 ORM 표준 JPA 프로그래밍] 13.1 트랜잭션 범위의 영속성 컨텍스트 (0) | 2021.09.21 |
|---|---|
| [자바 ORM 표준 JPA 프로그래밍] 12.5~10 명세, 사용자 정의 리포지토리, Web 확장... (0) | 2021.09.21 |
| [자바 ORM 표준 JPA 프로그래밍] 12.1~3 스프링 데이터 JPA 소개, 공통 인터페이스 기능 (0) | 2021.09.21 |
| [자바 ORM 표준 JPA 프로그래밍] 10.6 객체지향 쿼리 심화 (0) | 2021.09.13 |
| [자바 ORM 표준 JPA 프로그래밍] 10.5 네이티브 SQL (0) | 2021.09.13 |