[Spring Boot]Spring Data JPA 쿼리 메소드

이민재·2024년 6월 10일
0
post-thumbnail

JPA는 인터페이스로서 자바 표준 명세서이다. 인터페이스인 JPA를 사용하기 위해서는 구현체가 필요한데 대표적으로 Hibernate 등이 있다. 하지만 Spring에서는 JPA를 사용할때 이 구현체들을 직접 다루지않고, 구현체들을 좀 더 쉽게 사용하고자 추상화 시킨 Spring Data JPA라는 모듈을 이용하여 JPA 기술을 다룬다.

Spring Data JPA 쿼리 메소드

  • Repository 인터페이스에 간단한 네이밍 룰을 이용하여 메소드를 작성하면 원하는 쿼리를 실행할 수 있음

지원되는 접두어 키워드

지원되는 쿼리 조건자 키워드

쿼리 메소드 예시


Repository 예시

import com.example.web.sample.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.time.LocalDateTime;
import java.util.List;

public interface UserRepository extends JpaRepository<User, Long> {

    // 이름이 정확히 일치하는 사용자를 조회합니다.
    User findByName(String name);

    // 주어진 이메일을 가진 사용자의 존재 여부를 확인합니다.
    boolean existsByEmail(String email);

    // 주어진 이메일을 가진 사용자를 삭제하고 삭제된 레코드 수를 반환합니다.
    // 내부적으로 SELECT 이후 DELETE 쿼리를 수행하기 때문에 JpaRepository에서 제공하는 delete() 메소드를 이용하여 삭제하는 경우가 많음
    long removeByEmail(String email);

    // 이름과 이메일이 모두 일치하는 사용자를 조회합니다.
    User findByNameAndEmail(String name, String email);

    // 주어진 두 이름 중 하나와 일치하는 모든 사용자를 조회합니다. (메소드 명 수정 필요)
    List<User> findByNameOrName(String name1, String name2); // 메소드명이 올바르지 않습니다.

    // 지정된 시간 이후에 생성된 모든 사용자를 조회합니다.
    List<User> findByCreatedAtAfter(LocalDateTime yesterday);

    // 주어진 ID보다 큰 ID를 가진 모든 사용자를 조회합니다.
    List<User> findByIdGreaterThan(Long id);

    // 지정된 두 시간 사이에 생성된 모든 사용자를 조회합니다.
    List<User> findByCreatedAtBetween(LocalDateTime yesterday, LocalDateTime tomorrow);

    // 지정된 시간 이후 및 이전 또는 그날에 생성된 모든 사용자를 조회합니다.
    List<User> findByCreatedAtGreaterThanEqualAndCreatedAtLessThanEqual(LocalDateTime yesterday, LocalDateTime tomorrow);

    // ID가 null이 아닌 모든 사용자를 조회합니다.
    List<User> findByIdIsNotNull();

    // 주어진 이름 목록 중 하나와 일치하는 모든 사용자를 조회합니다.
    List<User> findByNameIn(List<String> names);

    // 주어진 문자로 시작하는 이름을 가진 모든 사용자를 조회합니다.
    List<User> findByNameStartingWith(String name);

    // 주어진 문자로 끝나는 이름을 가진 모든 사용자를 조회합니다.
    List<User> findByNameEndingWith(String name);

    // 주어진 문자를 포함하는 이름을 가진 모든 사용자를 조회합니다.
    List<User> findByNameContaining(String name);

    // 주어진 패턴에 일치하는 이름을 가진 모든 사용자를 조회합니다.
    List<User> findByNameLike(String name); // 사용자가 '%' 문자를 포함해야 합니다.
    
     // 주어진 이름과 일치하는 모든 사용자를 ID 기준 내림차순으로 정렬하여 반환
    List<User> findByNameOrderByIdDesc(String name);

    // 주어진 이름과 일치하는 모든 사용자를 ID 기준 내림차순으로 정렬하고, 같은 ID를 가진 사용자 내에서는 이메일 기준 오름차순으로 정렬하여 반환
    List<User> findByNameOrderByIdDescEmailAsc(String name);
}

페이징

UserRepository 인터페이스

import com.example.web.sample.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.time.LocalDateTime;
import java.util.List;

public interface UserRepository extends JpaRepository<User, Long> {

// 페이징
// 1. count 쿼리
// SELECT 쿼리 이후, count 쿼리가 추가적으로 동작
Page<User> findByName(String name, Pageable pageable);

// 2. count 쿼리 사용 안함
List<User> findByName(String name, Pageable pageable);

}

UserService 클래스

import com.example.web.sample.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    // 이름으로 사용자를 페이지네이션과 함께 검색하고 총 페이지 수를 포함한 결과 반환
    public Page<User> findUsersByNameWithPagination(String name, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findByName(name, pageable);
    }

    // 이름으로 사용자를 페이지네이션으로 검색하지만 총 페이지 수 계산을 생략
    public List<User> findUsersByNameWithoutCount(String name, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findByName(name, pageable);
    }
}

Controller 클래스

import com.example.web.sample.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    // 페이지네이션을 사용하여 이름으로 사용자 검색
    @GetMapping("/search")
    public Page<User> getUserByNameWithPagination(
            @RequestParam String name,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return userService.findUsersByNameWithPagination(name, page, size);
    }

    // 페이지네이션을 사용하여 이름으로 사용자 검색하지만 총 페이지 수 계산을 생략
    @GetMapping("/searchWithoutCount")
    public List<User> getUsersByNameWithoutCount(
            @RequestParam String name,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return userService.findUsersByNameWithoutCount(name, page, size);
    }
}
  1. findByName(String name, Pageable pageable): 이 메소드는 이름으로 사용자를 검색하고, Pageable 객체를 통해 요청된 페이지 번호와 페이지 크기에 따라 데이터를 반환합니다. Page 객체는 검색된 데이터뿐만 아니라 전체 페이지 수와 같은 추가적인 페이징 정보도 포함합니다.
  2. findByName(String name, Pageable pageable) 반환형 List<User>: 이 메소드는 총 결과 수 계산 없이 페이징된 데이터만 반환합니다. 총 결과 수를 계산하지 않기 때문에, 처리 성능이 더 빨라질 수 있으나, 사용자가 전체 페이지 수를 알 수 없습니다.

다른방법

import org.hibernate.criterion.Order;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;

/* 1. PageRequest 객체 생성 */
// 페이징 조건과 정렬 조건 설정
PageRequest pageRequest = new PageRequest(0, 10, new Sort(Direction.DESC, "id"));
Page<User> result = userRepository.findByNameStartingWith("name", pageRequest);

List<User> userList = result.getContent();  // 조회된 데이터
int totalPage = result.getTotalPages();     // 전체 페이지 수
boolean hasPrevious = result.hasPrevious(); // 이전 페이지 존재 여부
boolean hasNextPage = result.hasNextPage(); // 다음 페이지 존재 여부

/* 2. PageRequest static 메소드 이용 */
Page<User> result = userRepository.findByNameStartingWith("name", PageRequest.of(0, 10, Sort.by(Order.desc("id"))));

List<User> userList = result.getContent();  // 조회된 데이터
int totalPage = result.getTotalPages();     // 전체 페이지 수
boolean hasPrevious = result.hasPrevious(); // 이전 페이지 존재 여부
boolean hasNextPage = result.hasNextPage(); // 다음 페이지 존재 여부

스프링 MVC에서 JPA 페이징

스프링 MVC에서 편리하게 사용하도록 HandlerMethodArgumentResolver 제공

· page : 현재 페이지, 0부터 시작

· size : 한 페이지에 노출할 데이터 건 수 (default:20)

· sort : 정렬 조건 정의

※ spring.data.web.pageable.default-page-size=20 (기본 페이지 사이즈)

※ spring.data.web.pageable.max-page-size=2000 (최대 페이지 사이즈)

※ Controller에서 Pageable 객체를 파라미터로 받으면 특정 쿼리 파라미터를 자동으로 바인딩해서 pageable 객체를 생성해줌

※ @PageableDefault 어노테이션을 이용해 Pageable 객체의 Default 값을 컨트롤러 단위로 설정 가능

※ 여러 Pageable 객체를 받을 경우 @Qualifier 어노테이션을 이용해 "접두사_"로 분리해서 객체를 받을 수 있음

@RequestMapping(value = "/users", method = RequestMethod.GET)
public String list(Pageable pageable, Model model) {
    Page<User> page = memberService.findUsers(pageable);

    model.addAttribute("users", page.getContent());
    return "members/memberList";
}

/* @PageableDefault 어노테이션으로 Pageable 객체 기본값 적용 */
@RequestMapping(value = "/users", method = RequestMethod.GET)
public String list(@PageableDefault(page = 0, size = 100, 
        sort = “name”,  direction = Sort.Direction.DESC) Pageable pageable) {
    ..
}

/* 특정 쿼리 파라미터 바인딩 */
// Controler에서 Pageable 객체를 파라미터로 받으면 특정 쿼리 파라미터를 자동으로 바인딩해서 pageable 객체로 생성
http://localhost:8080/users?page=5&size=10&sort=user,desc

0개의 댓글