1. 학습 주제
이번에는 JPA를 한 단계 더 편하게 사용할 수 있는 Spring Data JPA 흐름을 정리했다.
이전까지는 EntityManager를 직접 다루면서 조회와 저장을 처리했다면, 이번에는 Repository 인터페이스만으로 기본 CRUD와 조건 조회, 페이징까지 연결하는 구조를 보게 되었다.
핵심은 다음 세 가지였다.
- JpaRepository를 상속받아 기본 CRUD를 바로 사용하는 방식
- 메서드 이름만으로 조건 조회 쿼리를 만드는 쿼리 메서드
- Pageable, Page를 사용한 페이징 처리
즉 이번 범위는
JPA를 더 적은 코드로, 더 스프링스럽게 사용하는 방식을 익히는 단계라고 볼 수 있다.
2. Spring Data JPA란 무엇인가
Spring Data JPA는
JPA를 더 쉽게 쓰도록 스프링이 제공하는 추상화 기술이다.
JPA 자체만 사용할 때는 보통 다음 작업을 자주 직접 한다.
- EntityManager 주입
- find, persist 호출
- JPQL 작성
- 결과 처리
Spring Data JPA는 이런 반복을 많이 줄여준다.
즉 개발자는:
- Repository 인터페이스 작성
- 엔티티 타입과 기본 키 타입 지정
- 필요한 경우 메서드 이름 규칙으로 조회 메서드 선언
정도만 해도 꽤 많은 기능을 사용할 수 있다.
3. JpaRepository가 하는 일
이번 구조에서 가장 중요한 출발점은 JpaRepository였다.
Repository 인터페이스가 JpaRepository<엔티티, PK타입>을 상속받으면
Spring Data JPA가 기본적인 데이터 접근 기능을 구현해서 제공한다.
예를 들어 별도 구현 없이도 다음 메서드들을 사용할 수 있다.
- findById()
- findAll()
- save()
- delete()
즉 Repository를 직접 구현 클래스까지 만들지 않아도,
인터페이스 선언만으로 기본 CRUD가 가능해진다.
이게 Spring Data JPA의 가장 큰 장점 중 하나다.
4. Repository, Service, Controller 흐름
이번 구조도 계층은 그대로 유지됐다.
- Controller: HTTP 요청과 응답 담당
- Service: 비즈니스 로직과 흐름 담당
- Repository: DB 접근 담당
다만 이전과 다른 점은 Repository 구현 방식이다.
예전 JPA에서는:
- Repository 안에서 EntityManager를 직접 쓰는 방식
이었다면,
이번에는:
- Repository 인터페이스가 JpaRepository를 상속하고
- Spring이 구현체를 자동으로 만들어주는 방식
으로 바뀐다.
즉 구조는 그대로지만,
DB 접근 코드 작성량이 크게 줄어드는 것이 핵심이다.
5. 메뉴 단건 조회
메뉴 상세 조회 흐름은 다음과 같다.
- 클라이언트가 메뉴 번호로 요청
- Controller가 Service 호출
- Service가 Repository의 findById() 호출
- 결과 엔티티를 DTO로 변환
- 응답 반환
여기서 중요한 점은 findById()가 Optional을 반환한다는 것이다.
즉 조회 결과가 없을 수도 있기 때문에
그 상황을 고려해서 처리해야 한다.
이번 구조에서는 값이 없으면 예외를 던지도록 되어 있었다.
즉 단순 조회도 “항상 값이 있다”가 아니라
없을 수도 있는 결과를 명시적으로 다루는 방식으로 연결된다.
6. 엔티티를 바로 응답하지 않고 DTO로 바꾸는 이유
서비스에서는 엔티티를 그대로 반환하지 않고 MenuResponseDTO로 바꾸고 있었다.
이렇게 하는 이유는 다음과 같다.
1. 응답 구조를 통제할 수 있다
필요한 필드만 골라서 보낼 수 있다.
2. 엔티티를 외부에 직접 노출하지 않는다
엔티티는 JPA가 관리하는 객체라서 내부 구조와 연관관계를 그대로 드러내기엔 부담이 있다.
3. 화면이나 API 목적에 맞는 형태로 바꾸기 쉽다
예를 들어 카테고리 정보를 응답에 포함할지 말지도 DTO에서 조정할 수 있다.
즉 DTO는
응답 전용 형태를 만들기 위한 안전한 중간 계층이라고 보면 된다.
7. ModelMapper를 사용하는 이유
이번 코드에서는 엔티티를 DTO로 바꿀 때 ModelMapper를 사용했다.
즉 개발자가 필드를 일일이 하나씩 복사하지 않고,
이름이 같은 필드들을 자동으로 매핑할 수 있게 해주는 도구다.
예를 들어:
- menuCode
- menuName
- menuPrice
- orderableStatus
같은 값들을 엔티티에서 DTO로 옮길 때 반복 코드를 줄일 수 있다.
설정에서는 private 필드까지 접근 가능하도록 하고,
필드 이름 기준 매칭을 켜두었다.
즉 ModelMapper는
엔티티와 DTO 사이의 반복적인 값 복사를 줄여주는 도구다.
8. Lombok을 사용하는 이유
엔티티와 DTO에 Lombok도 사용했다.
예를 들면:
- @Getter
- @NoArgsConstructor
- @RequiredArgsConstructor
- @Builder
같은 것들이다.
Lombok의 목적은 간단하다.
- getter
- 생성자
- builder
- 반복 보일러플레이트 코드
를 줄여서 코드 가독성을 높이는 것이다.
특히 JPA 엔티티에서는 기본 생성자가 필요하고,
외부에서 무분별하게 생성하지 못하게 접근 수준을 제어하는 경우가 많다.
그래서 @NoArgsConstructor(access = PROTECTED) 같은 구조가 자연스럽게 사용된다.
즉 Lombok은
코드를 짧게 만드는 도구이면서,
엔티티 생성 규칙을 더 명확히 표현하는 데도 도움이 된다.
9. 엔티티 안에 수정 메서드를 두는 이유
메뉴 엔티티 안에는 수정용 메서드도 있었다.
예:
- 전체 수정 메서드
- 가격 수정 메서드
- 이름 수정 메서드
이 구조는 중요하다.
예전에는 엔티티를 그냥 데이터 상자처럼 생각하기 쉽지만,
JPA에서는 엔티티를 “상태와 동작을 가진 객체”로 보는 편이 더 자연스럽다.
즉 값 변경도 아무 데서나 막 하는 게 아니라,
엔티티 스스로 자신의 상태를 바꾸는 메서드를 가지게 만드는 것이다.
이런 방식은:
- 도메인 규칙을 한곳에 모으기 쉽고
- 무분별한 setter 사용을 줄일 수 있고
- 객체지향적으로 의미가 더 분명하다
는 장점이 있다.
10. 카테고리 엔티티와 ManyToOne
메뉴 엔티티는 카테고리 엔티티를 참조하고 있었다.
즉 메뉴는:
- 메뉴 이름, 가격, 상태 같은 자기 정보
- 그리고 자신이 속한 카테고리
를 함께 가진다.
이 구조는 JPA 연관관계 매핑의 기본 형태다.
즉 메뉴 테이블의 category_code 외래키를
객체에서는 Category 참조로 표현한 것이다.
이걸 통해 얻는 장점은:
- DB 외래키를 직접 숫자로 다루기보다
- 객체 참조로 더 자연스럽게 다룰 수 있다는 점이다.
즉 “카테고리 코드 숫자”보다
“카테고리 객체”가 더 객체지향적인 표현이라는 뜻이다.
11. CategoryRepository도 따로 두는 이유
카테고리도 별도 Repository가 있었다.
이건 중요한 설계 포인트다.
메뉴를 저장하거나 수정할 때 카테고리 정보가 필요하면,
카테고리도 독립된 엔티티이기 때문에 독립된 접근 경로가 있어야 한다.
즉 연관된 엔티티라고 해서 모든 걸 메뉴 Repository 하나에 몰아넣지 않고,
각 애그리거트나 엔티티 관심사에 맞춰 접근 경로를 나누는 흐름을 볼 수 있다.
실무에서도:
- 메뉴는 메뉴 Repository
- 카테고리는 카테고리 Repository
처럼 역할을 나누는 경우가 많다.
12. 쿼리 메서드란 무엇인가
이번 범위의 핵심 중 하나는 쿼리 메서드였다.
쿼리 메서드는
메서드 이름만으로 조회 조건을 해석해서 쿼리를 만들어주는 기능이다.
예를 들어:
- 가격이 특정 값보다 큰 메뉴 조회
- 가격 내림차순 정렬
- 페이징 적용 조회
같은 동작을 메서드 이름으로 표현할 수 있다.
즉 개발자가 JPQL을 직접 적지 않아도
Spring Data JPA가 메서드 이름을 보고 쿼리를 생성한다.
13. 메서드 이름으로 쿼리를 만든다는 뜻
예를 들어 이런 메서드가 있다.
- findByMenuPriceGreaterThan(...)
- findByMenuPriceGreaterThanOrderByMenuPriceDesc(...)
이 메서드 이름은 그냥 임의로 지은 게 아니라,
Spring Data JPA가 규칙으로 읽는다.
예를 들면:
- findBy : 조회 시작
- MenuPrice : 어떤 필드를 기준으로
- GreaterThan : 초과 조건
- OrderByMenuPriceDesc : 가격 기준 내림차순 정렬
즉 메서드 이름이 길어 보이지만,
그 안에 조회 규칙이 전부 들어가 있다고 보면 된다.
14. 쿼리 메서드의 장점
1. 간단한 조건 조회를 빠르게 만들 수 있다
굳이 JPQL이나 SQL을 직접 안 적어도 된다.
2. 의도가 이름에 드러난다
무슨 조건인지 메서드명만 보고 알 수 있다.
3. 반복되는 단순 조회에 강하다
기본 CRUD와 단순 검색에서 특히 편하다.
즉 쿼리 메서드는
간단한 조회를 매우 빠르게 만들기 위한 Spring Data JPA의 강력한 기능이다.
15. 언제 쿼리 메서드를 쓰고, 언제 JPQL을 쓰는가
기준은 생각보다 단순하다.
쿼리 메서드가 잘 맞는 경우
- 조건이 단순하다
- 필드 비교 몇 개면 충분하다
- 정렬이나 페이징 정도만 필요하다
JPQL이 필요한 경우
- 조인이 복잡하다
- projection이 필요하다
- group by, having, fetch join 등 고급 조회가 필요하다
즉 쿼리 메서드는 편리하지만 모든 상황을 해결하는 건 아니다.
복잡도가 올라가면 JPQL이나 다른 방식이 더 적합하다.
16. 페이징과 Pageable
이번 범위에서 또 중요한 부분은 Pageable이다.
Spring Data JPA에서는 페이징 정보를 담는 객체로 Pageable을 사용한다.
이 객체 안에는 보통:
- 몇 페이지인지
- 한 페이지에 몇 개를 가져올지
- 어떤 기준으로 정렬할지
같은 정보가 들어간다.
즉 개발자는 메서드 파라미터로 Pageable만 받으면,
Spring Data JPA가 그 정보를 해석해서 적절한 페이징 쿼리를 만든다.
이게 큰 장점이다.
17. Page와 List 차이
단순 조회는 List로 충분할 수 있다.
하지만 페이징 조회는 Page를 쓰는 경우가 많다.
List
- 현재 가져온 데이터 목록만 있음
Page
- 목록 데이터
- 전체 개수
- 현재 페이지 정보
- 전체 페이지 수
- 다음 페이지 존재 여부
같은 부가 정보도 함께 가진다.
즉 화면에서 페이지 UI까지 만들려면
보통 Page가 더 적합하다.
그래서 이번 목록 조회는 Page<MenuResponseDTO>로 반환하고 있었다.
18. @PageableDefault
컨트롤러에서는 @PageableDefault도 사용했다.
이건 페이징 기본값을 정하는 역할이다.
예를 들면:
- 기본 페이지 크기
- 기본 정렬 기준
을 지정할 수 있다.
즉 클라이언트가 페이징 파라미터를 안 줘도
서버 쪽 기본 기준으로 조회가 가능하게 만들어준다.
이건 실무에서도 자주 쓰인다.
19. 메뉴 목록 전체 조회와 검색 조회의 차이
이번 흐름에서는 두 종류의 조회가 있었다.
전체 목록 조회
- findAll(pageable) 기반
- 메뉴 전체를 페이징해서 가져옴
조건 검색 조회
- 가격 기준 초과 조건
- 페이징 및 정렬 적용 가능
즉 Spring Data JPA에서는:
- 기본 목록은 findAll
- 조건 검색은 쿼리 메서드
- 둘 다 Pageable을 붙이면 페이징 가능
구조로 자연스럽게 확장할 수 있다.
20. DTO 변환과 Page.map()
목록 조회에서 받은 결과는 Page<Menu>였지만
응답은 Page<MenuResponseDTO>로 바꾸고 있었다.
이때 Page.map()을 사용했다.
이게 중요한 이유는,
단순히 리스트 내부 요소만 바꾸는 게 아니라
페이징 정보는 유지한 채 내용만 DTO로 변환할 수 있기 때문이다.
즉 Page 구조를 깨지 않고
엔티티 -> DTO 변환을 할 수 있다.
이 방식은 Spring Data JPA에서 매우 자주 쓰인다.
21. 이번 범위를 한 흐름으로 보면
이번 학습은 “Spring Data JPA를 실제 API 구조에 끼워 넣는 첫 단계”라고 볼 수 있다.
흐름은 다음과 같다.
- 엔티티와 DTO를 구분한다
- Repository는 JpaRepository를 상속한다
- Service는 Repository를 사용해 엔티티를 조회한다
- 엔티티를 DTO로 변환한다
- Controller는 DTO나 Page<DTO>를 응답한다
- 단순 조회는 쿼리 메서드로 처리한다
- 목록 조회는 Pageable로 페이징한다
즉 이전의 “JPA 개념 이해” 단계에서
이번에는 “실제 서비스 구조에 얹는 방법”으로 넘어온 셈이다.
22. 학습 흐름 정리
22.1 Spring Data JPA 기본 구조
- JpaRepository 상속
- 기본 CRUD 자동 제공
- 구현체 직접 작성 불필요
22.2 조회 API 구성
- 단건 조회: findById()
- 전체 조회: findAll(pageable)
- 조건 조회: 쿼리 메서드 사용
22.3 페이징 처리
- Pageable로 요청 조건 전달
- Page로 결과 + 페이징 정보 반환
- @PageableDefault로 기본값 설정
22.4 엔티티와 DTO 변환
- 엔티티는 JPA 관리 대상
- DTO는 응답용 구조
- ModelMapper로 변환
- Page.map()으로 페이징 결과 DTO화
23. 핵심 정리
- Spring Data JPA는 JPA 사용을 더 간단하게 해주는 스프링의 추상화 기술이다.
- JpaRepository를 상속하면 기본 CRUD 메서드를 바로 사용할 수 있다.
- Repository 구현 클래스를 직접 만들지 않아도 Spring Data JPA가 자동으로 구현해준다.
- 단건 조회는 findById(), 전체 목록 조회는 findAll(pageable)로 자연스럽게 처리할 수 있다.
- 쿼리 메서드는 메서드 이름만으로 조회 조건과 정렬을 표현하는 기능이다.
- 쿼리 메서드는 단순 조건 조회에 매우 강력하지만, 복잡한 조회는 JPQL이 더 적합하다.
- Pageable은 페이지 번호, 크기, 정렬 정보를 담는 객체이다.
- Page는 목록뿐 아니라 전체 개수와 페이지 정보까지 함께 담는다.
- 엔티티는 JPA가 관리하는 객체이고, DTO는 응답/전달용 객체이다.
- ModelMapper를 사용하면 엔티티와 DTO 사이의 반복적인 값 복사를 줄일 수 있다.
- Page.map()을 사용하면 페이징 정보는 유지한 채 엔티티를 DTO로 변환할 수 있다.
- 이번 범위의 핵심은 Spring Data JPA로 “실제 조회 API를 훨씬 적은 코드로 구성하는 방법”을 익히는 것이다.
'TIL > [TIL]' 카테고리의 다른 글
| TIL - Spring Security 기초, 세션과 토큰, Filter와 Interceptor, SSR과 CSR (0) | 2026.06.22 |
|---|---|
| [TIL]Spring Data JPA로 메뉴 등록, 수정, 삭제 구현하기 (0) | 2026.06.18 |
| [TIL]JPQL JOIN, Native Query, 그리고 Spring Data JPA 시작 (0) | 2026.06.16 |
| [TIL]JPQL의 Projection, Paging, Group Function 정리 (0) | 2026.06.15 |
| [TIL]JPA 연관관계의 주인, JPQL, 그리고 지연 로딩 (0) | 2026.06.12 |