스프링 부트 핵심 가이드 - Spring Data JPA

이건희·2024년 3월 11일
0

오늘은 Spring Data JPA 활용에 관한 부분이다. 이전에는 JPA가 무엇인지 알아보았고 오늘은 어떻게 활용하는지 알아보자.


JPQL

JPQL은 JPA Query Language의 줄임말로 JPA에서 사용할 수 있는 쿼리를 의미한다. JPQL의 문법은 SQL과 비슷하지만 테이블이나 컬럼의 이름을 사용하지 않고 엔티티의 이름과 필드 이름을 사용한다.

쿼리 메서드

Repository는 JpaRepository를 상속받는 것만으로도 다양한 CRUD 메서드를 제공한다. 하지만 이러한 메서드들 이외에도 별도의 메서드를 정의해서 사용하는 경우가 훨씬 많다. 이때 사용되는 것이 쿼리 메서드이다.

쿼리 메서드 생성

쿼리 메서드는 Subject와 Predicate로 구성된다. 예를 들자면 'find...By', 'exists...By'와 같은 형식이다. find, exists가 Subject이고 By의 뒷부분이 Predicate이다. 또한 AND, OR을 사용해 조건을 확장한다.

예제 코드

//리턴타입 + Subject + Predicate
List<Person> finddByLastnameAndEmail(String lastName, String email);

쿼리 메서드의 주제(Subject) 키워드

쿼리 메서드 Subject 부분에 사용할 수 있는 주요 키워드는 다음과 같다.

  • exists...By

    • 특정 데이터가 존재하는지 확인하는 키워드

    • 리턴 타입으로 boolean 값 사용

    • boolean existsByNumber(Long number);
  • count...By

    • 조회 쿼리 수행 후, 쿼리 결과로 나온 레코드의 개수를 리턴한다

    • long countByName(String name);
  • delete...By, remove...By

    • 삭제 쿼리 수행
    • 리턴 타입이 없거나 삭제한 횟수를 리턴
    • void deleteByNumber(Long number);
       long removeByName(String name);
  • ...First<number>..., ...Top<number>...

    • 쿼리를 통해 조회된 결과값의 개수를 제한 하는 키워드
    • 위 두개는 동일하며, By 사이에 위치한다.
    • 위 키워드는 여러 건을 조회할 때 사용하며, 단 건 조회 시에는 <number>를 생략하면 된다.
    • List<Product> findFirst5ByName(String name);
       List<Product> findTop10ByName(String name);

    쿼리 메서드의 조건자(Predicate) 키워드

    서술어 부분에 사용할 수 있는 키워드는 다음과 같다.

  • Is

    • 값의 일치를 조건으로 사용하는 키워드
    • 생략되는 경우가 많으며 Equals와 동일한 기능 수행
    • Product findByNumberIs(Long number);
       Product findByNumberEquals(Long number);
       //모두 findByNumber와 동일
  • (Is)Not

    • 값의 불일치를 조건으로 사용
    • Is는 생략하고 Not만 사용 가능
    • Product findByNumberIsNot(Long number);
       Product findByNumberNot(Long number);
  • (Is)Null, (Is)NotNull

    • 값이 null인지 검사하는 키워드
    • List<Product> findByUpdatedAtNull();
       List<Product> findByUpdatedAtIsNull();
       List<Product> findByUpdatedAtNotNull();
       List<Product> findByUpdatedAtIsNotNull();
  • (Is)True, (Is)False

    • boolean 타입으로 지정된 컬럼값을 확인하는 키워드
    • Product findByisActiveTrue();
       Product findByisActiveIsTrue();
       Product findByisActiveFalse();
       Product findByisActiveIsFalse();
  • And, Or

    • 여러 조건을 묶을 때 사용한다.
    • Product findByNumberAndName(Long number, String name);
       Product findByNumberOrName(Long number, String name);
       
  • (Is)GreaterThan, (Is)LessThan, (Is)Between

    • 숫자나 datetime 컬럼을 대상으로 비교 연산에 사용하는 키워드
    • GreaterThan, LessThan은 초과/미만, 경계값을 포함하려면 Equal 키워드 추가
    • List<Product> findByPriceGreaterThan(Long price);
       List<Product> findByPriceGreaterThanEqual(Long price);
       List<Product> findByPriceLessThan(Long price);
       List<Product> findByPriceLessThanEqual(Long price);
       List<Product> findByPriceBetween(Long lowPrice, Long highPrice);
  • (Is)StartingWith(==StartsWith), (Is)EndingWith(==EndsWith), (Is)Containing(==Contains), (Is)Like

    • 값에서 일부 일치 여부를 확인하는 키워드
    • SQL에서의 '%'와 동일한 역할을 한다.
    • Containing은 문자열의 양 끝, StartingWith은 문자열의 앞, EndingWith은 문자열 끝에 '%'가 배치된다.
    • Like는 코드 수준에서 메서드를 호출하면서 값에 '%'를 명시적으로 입력해야 한다.
    • List<Product> findByNameLike(String name);
       List<Product> findByNameContains(String name);
       List<Product> findByNameStartsWith(String name);
       List<Product> findByNameEndsWith(String name);

정렬

기본 쿼리 메서드의 이름을 통해 페이징 처리도 가능하지만 다른 방법들도 많이 사용된다. 한번 살펴보자.

쿼리문에서 정렬

일반적으로 쿼리문에서 정렬을 사용할 때는 ORDER BY를 많이 사용한다.

// Asc: 오름차순, Desc: 내림차순
List<Product> findByNameOrderByNumberAsc(String name);
List<Product> findByNameOrderByNumberDesc(String name);

위의 코드를 해석하자면, '상품정보를 이름으로 검색 후 상품 번호로 오름차순(내림차순) 정렬을 수행' 한다는 뜻이다.

또한 여러가지 기준을 적용하고 싶으면 AND를 생략하고 조건을 붙이면 된다.

List<Product> findByNameOrderByPriceAscStockDesc(String name);

위 코드는 '상품 가격을 기준으로 오름차순 정렬 후, 재고수량을 기준으로 내림차순 정렬' 한다는 의미이다.

매개변수를 활용한 쿼리 정렬

쿼리 메서드에 정렬 키워드를 삽입해서 정렬을 수행할 수도 있지만, 매개변수를 활용해 정렬할 수도 있다.

List<Product> findByName(String name, Sort sort);

productRepository.findByName("펜", Sort.by(Order.asc("price")));
productRepository.findByName("펜", Sort.by(Order.asc("price"), Order.desc("stock")));

Sort 객체를 전달하여 메서드를 정렬하는 코드이다. Sort 클래스는 내부 클래스로 정의돼 있는 Order 객체를 활용해 정렬 기준을 생성한다. 여러 정렬 기준을 사용할 경우 콤마를 사용하여 구분한다.


페이징 처리

페이징이란 데이터베이스의 레코드를 개수로 나눠 페이지를 구분하는 것을 의미한다. 보통 게시판 프로젝트나 CRUD 구현에서 자주 볼수 있다.

JPA에서는 페이징 처리를 위해 Page와 Pageable을 사용한다.

Page<Product> findByName(String name, Pageable pageable);

Page<Product> productPage = findByName("펜", PageRequest.of(0, 2));

위와 같이 메서드에서 매개변수로 Pageable 객체를 받는다. 호출에는 PageRequest를 넘겨 주었는데, PageRequest는 Pageable의 구현체이다.

PageRequest 오버로딩

PageRequest는 of 메서드를 통해 PageRequest 객체를 생성한다. of 메서드는 매개변수에 따라 다양한 형태로 오버로딩 되어 있다.

  • of(int page, int size)

    • 페이지 번호(0부터 시작), 페이지당 데이터 개수

    • 데이터를 정렬하지 않음

  • of(int page, int size, Sort)

    • 페이지 번호, 페이지당 데이터 개수, 정렬
  • of(int page, int size, Direction, String... properties)

    • 페이지 번호, 페이지당 데이터 개수, 정렬 방향, 속성(컬럼)

    • Sort.by(direction, properties)에 의해 정렬

또한 Page 객체를 그대로 출력하면 값을 보여주지 않고 몇 번째 페이지에 해당하는지만 확인할 수 있는데, 세부적인 값을 보려면 아래와 같이 사용한다.

Page<Product> productPage = findByName("펜", PageRequest.of(0, 2)));
System.out.println(productPage.getContent());

위와 같이 getContent()를 사용하여 배열로 출력 가능하다.


@Query 어노테이션

위의 예시들과 같이 메서드 이름만으로 쿼리 메서드를 생성 할수도 있지만, @Query 어노테이션을 사용해 직접 JPQL을 작성할 수도 있다.

@Query("SELECT p FROM Product AS p WHERE p.name = ?1")
List<Product> findByName(String name);
//소문자로 작성 가능하며 AS는 생략 가능
  • 조건문에서 사용한 '?1'은 파라미터를 전달 받기 위한 인자

  • 1은 첫 번째 파라미터를 의미한다.

  • 하지만 이는 파라미터의 순서가 바뀌면 오류가 발생할 수도 있으니 @Param 어노테이션을 사용한다.

    • @Query("SELECT p FROM Product p WHERE p.name = :name")
       List<Product> findByNameParam(@Param("name") String name);

또한 @Query 사용 시 특정 원하는 컬럼만 값 추출이 가능하다.

@Query("SELECT p.name, p.pricec, p.stock FROM Product p WHERE p.name = :name")
List<Object[]> findByNameParam2(@Param("name") String name);

QueryDSL

메서드의 이름과 @Query 어노테이션을 사용하면 직접 문자열을 입력하기 때문에 컴파일 시점에 에러를 잡지 못하고 런타임 에러가 발생할 수 있다. 이 같은 문제를 해결하기 위해 QueryDSL이 사용된다.

QueryDSL은 문자열이 아니라 코드로 쿼리를 작성할 수 있도록 도와준다.

..QueryDSL은 추후에 알아보도록 하자..

profile
광운대학교 정보융합학부 학생입니다.

0개의 댓글