본문 바로가기
ZB 백엔드 스쿨/블로그 과제

스프링 핵심가이드) 북스터디 4주차 : 08장 Spring Data JPA

by son_i 2023. 10. 16.
728x90

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 플러그인 추가.