08 Spring Data JPA 활용
8.2 JPQL(JPA Query Language) : JPA에서 사용할 수 있는 쿼리 의미.
JPQL은 엔티티 객체를 대상으로 수행하는 쿼리이기 때문에 매핑된 엔티티의 이름과 필드의 이름을 사용.
ex) SELECT p FROM Product(엔티티 타입) p WHERE p.number(엔티티 속성) = ?1;
SQL은 테이블이나 칼럼의 이름 사용.
8.3 쿼리 메서드
리포지토리는 JpaRepository를 상속하는 것 만으로 다양한 CRUD 메소드 제공.
8.3.1 쿼리 메서드의 생성
쿼리메서드는 동작을 결정하는 주제(Subject)와 서술어(Predicate)로 구분.
find...By, exists...By 키워드로 쿼리의 주제를 정함.
By는 서술어의 시작을 나타내는 구분자 역할.
서술어 부분은 검색 및 정렬 조건을 지정. 엔티티의 속성으로 정의할 수 있고, AND / OR 통해 조건 확장도 가능.
리포지토리 쿼리 메서드 생성 예
// (리턴 타입) + {주제 + 서술어(속성)} 구조의 메소드
List<Person> findByLastnameAndEmail(String, lastName, String email);
서술어에 들어가는 엔티티의 속성 식은 엔티티에서 관리하고 있는 속성(필드)만 참조할 수 있음.
8.3.2 쿼리 메서드의 주제 키워드
- find...By
- read...By
- get...By
- query...By
- search...By
- stream...By
- exists...By : 특정 데이터가 존재하는지 확인. 리턴타입 boolean
- count...By : 조회 쿼리 수행후 레코드 갯수 리턴.
- delete...By, remove...By : 삭제 쿼리 수행. 리턴타입 없거나 삭제한 횟수 리턴
- ...First<number>..., ...Top<number>... : 쿼리를 통해 조회된 결괏값의 개수 제한 키워드. 동일 동작 수행하며 주제와 By 사이에 위치. 한 번의 동작으로 여러 건을 조회할 때 사용되며, 단 건으로 조회하기 위해서는 <number> 생략하면됨.
List<Product> findFirst5ByName(String name);
List<Product> findTop10ByName(String name);
...에는 도메인(엔티티) 표현할 수 있으나 리포지토리에서 이미 도메인을 지정하고 메서드를 쓰기 때문에 중복이라 생략.
리턴타입으로는 Collection이나 Stream에 속한 하위 타입 설정 가능.
8.3.3 쿼리 메서드의 조건자 키워드
- ls : 값의 일치를 조건으로 사용하는 조건자 키워드. 생략되는 경우 많으며 Equals와 동일 기능 수행.
ex) Product findByNumberIs(Long number);
Product findByNumberEquals(Long number); // findByNumber 메서드와 동일
- (Is)Not : 값의 불일치를 조건으로 사용하는 키워드. Is는 생략하고 Not만 사용하는 것도 가능
ex) Product findByNumberIsNot(Long number); == Product findByNumberNot(Long number);
- (Is)Null, (Is)NotNull : 값이 null인지 검사하는 키워드
ex) List<Product> findByUpdateAtNull();
List<Product> findByUpdateAtIsNull();
List<Product> findByUpdateAtNotNull();
List<Product> findByUpdateAtIsNotNull();
- (Is)True, (Is)False : boolean 타입으로 지정된 컬럼 확인하는 키워드.
ex) Product findByisActiveTrue();
Product findByisActiveIsTrue();
Product findByisActiveFalse();
Product findByisActiveIsFalse();
- And, Or : 여러 조건을 묶을 때 사용
ex) Product findByNumberAndName(Long number, String name);
- (Is)GreaterThan, (Is)LessThan, (Is)Between : 숫자나 datatime 칼럼을 대상으로 한 비교 연산에 사용
앞 두개는 비교 대상에 대한 초과/미만의 개념으로 연산 수행. 경계값 포함시 Equal키워드 추가
ex) List<Product> findByPriceIsGreaterThan(Long price);
List<Product> findByPriceGreaterThan(Long price);
List<Product> findByPriceGreaterThanEqual(Long price);
- (Is)StartingWith(==StartsWith), (Is)EndingWith(==EndsWith), (Is)Containing(==Contains), (Is)Like
: 컬럼 값에서 일부 일치 여부 확인. SQL의 %와 동일한 역할.
Containing은 문자열의 양 끝, StartingWith는 문자열의 앞, EndingWith은 문자열의끝에 %가 배치됨.
Like는 코드 수준에서 메소드를 호출할 때 전달하는 값에 % 명시적으로 입력.
8.4 정렬과 페이징 처리
8.4.1 정렬 처리하기
일반적인 쿼리문에서 ORDER BY 사용.
List<Product> findByNameOrderByNumberAsc(String name);
List<Product> findByNameOrderByNumberDesc(String name)
조건을 여러 개 사용할 때 And, Or을 쓰지만 정렬구문은 쓰지 않고 우선순위 기준으로 차례로 작성하면 됨.
List<Product> findByNameOrderByPriceAscStockDesc(String name);
Price기준 오름차순 먼저, 재고수량 기준 내림차순 정렬 수행.
위의 방법은 메서드의 이름이 길어질 수록 가독성 저하.
- 매개변수를 활용해 정렬
List<Product> findByName(String name, Sort sort);
// Sort 객체를 활용해 매개변수로 받아들인 정렬기준을 가지고 쿼리문 작성.
ex) productRepository.findByName("펜", Sort.by(Order.asc("price")));
productRepository.findByName("펜", Sort.by(Order.asc("price"), Order.desc("stock")));
쿼리 메서드를 정의하는 단계에서는 코드가 줄어들지만 호출하는 곳에서는 코드가 길어져 가독성이 떨어짐.
Sort부분을 하나의 메서드로 분리해서 쿼리 메서드를 호출하는 방법도 가능.
8.4.2 페이징 처리
- 페이징 : DB 레코드를 개수로 나눠 페이지를 구분하는 것.
- JPA에서는 페이징 처리를 위해 Page와 Pageable을 사용.
Page<Product> findByName(String name, Pageable pageable);
리턴타입으로 Page를 설정해야하고 매개변수에는 Pageable타입의 객체를 정의.
호출하는 쪽에서는 아래와 같이 작성
Page<Product> productPage = productRepository.findByName("펜", PageRequest.of(0, 2));
PageRequest는 of 메서드를 통해 PageRequest 객체를 생성.
of(int page, int size) : 페이지 번호(0부터 시작), 페이지당 데이터 갯수 - 데이터를 정렬하지 않음.
of(int page, int size, Sort) : 페이지 번호, 페이지당 데이터 갯수, 정렬 - sort에 의해 정렬
of(int page, int size, Direction, String ... properties) : 페이지 번호, 페이지당 데이터 갯수, 정렬 방향, 속성(칼럼) : Sort.by(direfction, properties)에 의해 정렬.
8.5 @Query 어노테이션
DB에서 값을 가져올 때 메서드 이름 만으로 쿼리메서드 생성도 가능하고 @Query 어노테이션을 통해서도 가능.
@Query("SELECT p FROM Product AS p WHERE p.name = ?1")
List<Product> findByName(String name);
?1 : 은 파라미터를 전달받기 위한 인자에 해당. 1은 첫 번째 파라미터를 의미.
파라미터 순서 바뀌면 오류 가능성 -> @Param 이용
@Query("SELECT p FROM Product AS p WHERE p.name = :name")
List<Product> findByName(@Param("name") String name);
파라미터를 바인딩 하는 방식으로 메서드를 구현하면 코드의 가독성이 높아지고 유지보수 수월.
- @Query를 사용하면 엔티티 타입이 아니라 원하는 칼럼의 값만 추출 가능.
@Query("SELECT p.name, p.price, p.stock FROM Product AS p WHERE p.name = ?1")
List<Product> findByName(String name);
8.6 QueryDSL 적용
메서드의 이름을 기반으로 생성하는 JPQL의 한계는 @Query를 통해 대부분 해소 가능하지만 직접 문자열을 입력하기 때문에 컴파일 시점에 에러를 잡지 못 하고 런타임 에러가 발생할 수 있음. -> 쿼리의 문자열이 잘못된 경우 어플리케이션 실행후 로직 실행후에 오류 발견.
=> 위 같은 문제 해결하기 위해 QueryDSL 사용.
- QueryDSL : 문자열이 아니라 코드로 쿼리를 작성할 수 있게 해줌.
8.6.1 QueryDSL이란 ?
정적타입을 이용해 SQL과 같은 쿼리를 생성할 수 있도록 지원하는 프레임워크.
문자열이나 XML 파일을 통해 쿼리를 작성하는 대신 QueryDSL이 제공하는 플루언트API를 활용해 쿼리 생성 가능.
8.6.2 QueryDSL의 장점
- IDE가 제공하는 코드 자동완성기능 사용가능
- 문법적으로 잘못된 커리 잡아줌.
- 고정된 SQL쿼리 작성하지 않아서 동적으로 쿼리 생성 가능
- 코드로 작성해서 가독성 미 생산성 향상
- 도메인 타입과 프로퍼티를 안전하게 참조 가능
8.6.3 QueryDSL을 사용하기 위한 프로젝트 설정
1. pom.xml에 의존성 추가
2. <plugins>에 QueryDSL을 사용하기 위한 APT 플러그인 추가.
'ZB 백엔드 스쿨 > 블로그 과제' 카테고리의 다른 글
스프링 핵심가이드) 북스터디 6주차 : 10장 유효성 검사와 예외처리 (0) | 2023.10.29 |
---|---|
스프링 핵심가이드) 북스터디 5주차 : 09장 (0) | 2023.10.20 |
스프링 핵심가이드) 북스터디 3주차 : 06장 (1) | 2023.10.08 |
스프링 핵심가이드) 북스터디 2주차 : 04~05장 API를 작성하는 다양한 방법 (0) | 2023.09.30 |
스프링 핵심가이드) 북스터디 1주차 : 02~03장 (0) | 2023.09.24 |