현대오토에버 모빌리티 SW 스쿨 웹/앱 스프링부트 JpaRepository
JpaRepository 어노테이션
JPA는 "Java Persistence API"의 약자로, 자바 객체를 관계형 데이터 베이스에 영속적으로 저장하고 조회할 수 있는 ORM 기술에 대한 표준 명세를 의미한다.
JPA를 통해 개발자는 SQL쿼리를 작성하지 않고도 객체를 통해 데이터베이스를 조작할 수 있으며, 객체 지향적인 코드 작성과 유지 보수성이 향상된다.
기본적으로 Entity클래스를 작성한 후 Repository 인터페이스를 생성해야 하는데 Springboot에서 기본적인 작업을 도와주는 JPARepository 인터페이스가 있다!
JpaRepository는 CRUD(Create, Read, Update, Delete) 작업과 페이징 및 정렬 기능을 제공하는 다양한 메서드를 포함하고 있다. 아래 표는 JpaRepository에서 제공하는 기본 메서드들을 설명한 것이다.
save(S entity) | 엔티티를 저장하거나 업데이트. | S (저장된 엔티티) |
saveAll(Iterable<S> entities) | 여러 엔티티를 저장하거나 업데이트. | List<S> |
findById(ID id) | ID로 엔티티를 조회. | Optional<T> |
existsById(ID id) | ID로 엔티티의 존재 여부를 확인. | boolean |
findAll() | 모든 엔티티를 조회. | List<T> |
findAllById(Iterable<ID> ids) | 여러 ID로 엔티티 목록을 조회. | List<T> |
count() | 저장된 엔티티의 총 개수를 반환. | long |
deleteById(ID id) | ID로 엔티티를 삭제. | void |
delete(T entity) | 특정 엔티티를 삭제. | void |
deleteAll(Iterable<? extends T> entities) | 여러 엔티티를 삭제. | void |
deleteAll() | 모든 엔티티를 삭제. | void |
findAll(Sort sort) | 정렬된 모든 엔티티를 조회. | List<T> |
findAll(Pageable pageable) | 페이징된 모든 엔티티를 조회. | Page<T> |
flush() | 변경 내용을 데이터베이스에 즉시 반영. | void |
saveAndFlush(S entity) | 엔티티를 저장한 후, 즉시 데이터베이스에 반영. | S (저장된 엔티티) |
deleteAllInBatch() | 데이터베이스와 연결된 모든 엔티티를 배치 모드로 삭제. | void |
deleteInBatch(Iterable<T> entities) | 데이터베이스와 연결된 여러 엔티티를 배치 모드로 삭제. | void |
getOne(ID id) | ID로 엔티티를 즉시 로드하지 않고 프록시 객체로 반환. (실제로 사용할 때 데이터베이스 접근) | T |
설명:
- T: 엔티티 클래스의 타입 (예: Member)
- ID: 엔티티의 기본 키 타입 (예: Long)
- S: 저장할 엔티티의 타입
- Optional<T>: 엔티티가 존재하지 않을 경우를 처리하기 위해 Optional로 감싸진 결과 반환
- List <T>: 여러 엔티티의 목록을 반환
- Page <T>: 페이징 된 엔티티 결과를 반환
주요 기능
- CRUD 작업: save(), findById(), delete()와 같은 메서드를 통해 기본적인 CRUD 작업을 쉽게 수행할 수 있다.
- 페이징 및 정렬: findAll(Pageable pageable), findAll(Sort sort) 메서드를 사용하여 데이터를 페이징 처리하거나 정렬된 상태로 가져올 수 있다.
- 데이터 동기화: flush(), saveAndFlush()를 사용하여 데이터베이스와 즉시 동기화할 수 있다.
- Optional: findById()는 조회한 값이 존재하지 않을 수 있으므로 Optional <T>로 반환되어, null 처리를 간편하게 할 수 있다.
- 메서드 작성 규칙
method | 설명 |
findBy로 시작 | 쿼리를 요청하는 메서드 임을 알림 |
countBy로 시작 | 쿼리 결과 레코드 수를 요청하는 메서드 임을 알림 |
위의 findBy에 이어 해당 Entity 필드 이름을 입력하면 검색 쿼리를 실행한 결과를 전달한다.
SQL의 where절을 메서드 이름을 통해 전달한다고 생각하면 된다.
메서드의 반환형이 Entity 객체이면 하나의 결과만을 전달하고, 반환형이 List라면 쿼리에 해당하는 모든 객체를 전달한다.
- 메서드에 포함할 수 있는 키워드
메서드 이름 키워드 | 샘플 | 설명 |
And | findByEmailAndUserId(String email, String userId) | 여러필드를 and 로 검색 |
Or | findByEmailOrUserId(String email, String userId) | 여러필드를 or 로 검색 |
Between | findByCreatedAtBetween(Date fromDate, Date toDate) | 필드의 두 값 사이에 있는 항목 검색 |
LessThan | findByAgeGraterThanEqual(int age) | 작은 항목 검색 |
GreaterThanEqual | findByAgeGraterThanEqual(int age) | 크거나 같은 항목 검색 |
Like | findByNameLike(String name) | like 검색 |
IsNull | findByJobIsNull() | null 인 항목 검색 |
In | findByJob(String … jobs) | 여러 값중에 하나인 항목 검색 |
OrderBy | findByEmailOrderByNameAsc(String email) | 검색 결과를 정렬하여 전달 |
- 페이지네이션
Query 메소드의 입력변수로 위와 같이 Pageable 변수를 추가하면 Page타입을 반환형으로 사용할 수 있다.
Pageable 객체를 통해 페이징과 정렬을 위한 파라미터를 전달한다. Pageable 입력 변수는 아래와 같이 Controller에서부터 전달받아야 한다.
query parameter 명 | 설명 |
page | 몇번째 페이지 인지를 전달 |
size | 한 페이지에 몇개의 항목을 보여줄것인지 전달 |
sort | 정렬정보를 전달. 정렬정보는 필드이름,정렬방향 의 포맷으로 전달한다. 여러 필드로 순차적으로 정렬도 가능하다. 예: sort=created At,desc&sort=userId,asc |
- controller
@RequestMapping("/selectByName")
public String selectByName(@RequestParam("name") String search, @RequestParam("page") String page, Model model) {
System.out.println("/----------" + search + "------------/");
System.out.println("/----------" + page + "------------/");
String name = search + "%";
Sort sort = Sort.by(Sort.Order.desc("id"));
int nPage = Integer.parseInt(page) - 1;
Pageable pageable = PageRequest.ofSize(10).withPage(nPage).withSort(sort);
Page<Member> result = memberService.selectNameLike(name, pageable);
List<Member> content = result.getContent();
long totalElements = result.getTotalElements();
int totalPages = result.getTotalPages();
int size = result.getSize();
int pageNumber = result.getNumber() + 1; // 0부터 시작하므로
int numberOfElements = result.getNumberOfElements(); // content의 개수
model.addAttribute("members", content);
model.addAttribute("totalElements", totalElements);
model.addAttribute("totalPages", totalPages);
model.addAttribute("size", size);
model.addAttribute("pageNumber", pageNumber);
model.addAttribute("numberOfElements", numberOfElements);
return "selectList";
}
- repository
package com.sample.spring.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.sample.spring.domain.Member;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
// crud자동 생성
Page<Member> findByNameLike(String keyword, Pageable pageable);
}
- service
package com.sample.spring.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import com.sample.spring.domain.Member;
import com.sample.spring.repository.MemberRepository;
@Service
public class MemberService {
@Autowired
private MemberRepository memberRepository;
public List<Member> selectAll() {
return memberRepository.findAll(Sort.by(Sort.DEFAULT_DIRECTION.DESC, "id"));
}
public Member insert(Member member) {
Member returnMember = memberRepository.save(member);
return returnMember;
}
public Optional<Member> view(Long id) {
Optional<Member> member = memberRepository.findById(id);
return member;
}
public void delete(Long id) {
memberRepository.deleteById(id);
}
public Page<Member> selectNameLike(String search, Pageable pageable) {
Page<Member> member = memberRepository.findByNameLike(search, pageable);
return member;
}
}
- selectList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="jakarta.tags.core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
총 글의 개수: ${totalElements }<br>
총 페이지: ${totalPages }<br>
사이즈: ${size }<br>
페이지 넘버: ${pageNumber }<br>
number of element: ${numberOfElements }<br>
<c:forEach var="member" items="${members}">
${member.name} / ${member.createDate} /${member.email }
<hr>
</c:forEach>
</body>
</html>
- 결과화면
한 페이지에 10개의 데이터씩 출력되고 요청한 주소에 name은 따로 적지 않아 조건 없이 모두 출력되는 모습이다.
다음은 name에 jong를 써주었다. controller에서 입력한 이름 뒤에 %를 붙여서 해당 검색한 이름으로 시작하는 모든 이름들을 검색하여 출력해 준다.
- dbeaver
- Pageable 객체는 페이지 번호, 크기, 정렬 등의 정보를 담고 있으며, 컨트롤러에서 클라이언트로부터 받은 요청에 따라 서비스에 전달된다.
- Page <Member>는 JPA가 데이터베이스에서 검색된 데이터를 페이지 단위로 가져와 반환하는 객체다. 이 객체는 페이지 정보(현재 페이지, 총 페이지 수, 페이지 당 항목 수 등)를 포함하고 있다.
이렇게 계층적으로 Controller → Service → Repository로 호출이 이루어지며, JPA는 내부적으로 Pageable에 설정된 조건에 따라 쿼리를 생성해 페이지별 결과를 가져온다.