[Spring/숙련] RestTemplate, JPA 연관관계, 지연 로딩/영속성 전이

JM·1일 전

1. RestTemplate

RestTemplate

Spring에서 서버와 서버 간 HTTP 요청을 쉽게 처리할 수 있도록 제공하는 기능

기존에는 브라우저에서 서버로 요청을 보내는 구조였다면, RestTemplate을 사용하면 내 서버가 다른 서버에 요청을 보내는 Client 역할을 할 수 있음

Client
  ↓
내 서버
  ↓ RestTemplate
외부 서버 / API 서버

주요 용도

  • 외부 API 호출
  • 다른 서버와의 통신
  • Open API 연동

2. RestTemplate GET 요청

getForEntity()

GET 방식으로 서버에 요청하고 응답을 ResponseEntity로 반환

restTemplate.getForEntity(uri, ItemDto.class)

UriComponentsBuilder

URI를 편리하게 생성하기 위한 기능

UriComponentsBuilder
    .fromUriString("http://localhost:7070")
    .path("/api/server/get-call-obj")
    .queryParam("query", query)
    .build()
    .toUri()

핵심

  • fromUriString() → 기본 URL 설정
  • path() → 요청 경로 설정
  • queryParam() → Query String 추가
  • build() → URI 생성
  • toUri() → URI 객체 변환

응답 JSON을 Java 객체로 자동 변환할 수 있다는 점도 중요한 특징


3. RestTemplate POST 요청

postForEntity()

POST 방식으로 서버에 데이터를 전달할 때 사용

restTemplate.postForEntity(
    uri,
    requestData,
    ItemDto.class
)

특징

  • 첫 번째 인자 → 요청 URI
  • 두 번째 인자 → HTTP Body에 전달할 데이터
  • 세 번째 인자 → 응답 데이터를 변환할 타입

Java 객체를 Body에 전달하면 JSON 형태로 자동 변환


4. RestTemplate exchange()

GET, POST 등의 HTTP Method뿐만 아니라 Header, Body 등의 요청 정보를 직접 구성해야 할 때 사용

RequestEntity<User> requestEntity =
    RequestEntity
        .post(uri)
        .header("X-Authorization", token)
        .body(user);

restTemplate.exchange(requestEntity, String.class);

RequestEntity

HTTP 요청에 필요한 정보를 하나의 객체로 구성

  • URI
  • HTTP Method
  • Header
  • Body

핵심

getForEntity(), postForEntity()보다 세부적인 HTTP 요청 설정이 필요한 경우 exchange() 사용


Open API

5. Open API

외부 서비스에서 제공하는 기능을 다른 개발자가 사용할 수 있도록 공개한 API

이번 실습에서는 Naver Shopping Open API를 사용해 상품 검색 기능 구현

API 사용 과정

API 사용 신청
 ↓
Client ID / Client Secret 발급
 ↓
API 요청
 ↓
JSON 응답
 ↓
Java 객체 변환

상품 검색 결과에서 사용한 주요 데이터

  • title → 상품명
  • link → 상품 링크
  • image → 상품 이미지
  • lprice → 최저가

API 인증 정보

  • Client ID
  • Client Secret

Header에 인증 정보를 포함하여 요청


JPA Entity 연관관계

6. DB 연관관계와 Entity 연관관계

DB에서는 외래 키(FK)를 이용하여 테이블 간 관계를 표현

User
  ↑
  │ FK
Food

DB 테이블은 JOIN을 사용하기 때문에 어느 테이블을 기준으로 조회하더라도 원하는 데이터를 조회 가능

반면 JPA Entity에서는 객체 간 참조 관계를 직접 설정해야 함

핵심 차이

DB

  • 외래 키를 이용한 관계
  • JOIN을 통한 조회
  • 방향 개념 없음

Entity

  • 객체 참조를 통한 관계
  • 참조하는 방향 존재
  • 단방향 / 양방향 관계

7. 연관관계의 주인

JPA에서 연관관계를 설정할 때 가장 중요한 개념

외래 키를 실제로 관리하는 Entity

연관관계의 주인만 외래 키를 등록, 수정, 삭제할 수 있음

@JoinColumn

외래 키의 주인이 사용하는 애너테이션

@JoinColumn(name = "user_id")
private User user;

mappedBy

양방향 관계에서 연관관계의 주인이 아닌 쪽에 사용

@OneToMany(mappedBy = "user")
private List<Food> foodList;

mappedBy의 값은 연관관계의 주인이 가지고 있는 필드명

핵심

@JoinColumn
→ 연관관계의 주인

mappedBy
→ 연관관계의 주인이 아님

1:1 관계

8. @OneToOne

Entity 간 1 대 1 관계를 표현

@OneToOne
@JoinColumn(name = "user_id")
private User user;

특징

1:1 관계에서는 N의 관계가 없기 때문에 외래 키의 주인을 직접 결정해야 함

양방향 관계

@OneToOne
@JoinColumn(name = "user_id")
private User user;
@OneToOne(mappedBy = "user")
private Food food;

N:1 관계

9. @ManyToOne

Entity 간 N 대 1 관계를 표현

예를 들어 여러 음식이 하나의 고객에게 연결되는 경우

Food → User

Food 1 ─┐
Food 2 ─┼→ User
Food 3 ─┘

일반적으로 N 쪽 Entity가 외래 키의 주인

@ManyToOne
@JoinColumn(name = "user_id")
private User user;

양방향 관계

// Food
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
// User
@OneToMany(mappedBy = "user")
private List<Food> foodList;

가장 많이 사용하는 연관관계

실제 애플리케이션에서도 @ManyToOne을 자주 사용


1:N 관계

10. @OneToMany

Entity 간 1 대 N 관계를 표현

User
 ↓
Food
Food
Food

주의점

1:N 관계에서는 외래 키가 N 쪽 테이블에 존재하지만, 1 쪽 Entity가 연관관계의 주인이 될 수 있음

이 경우 추가적인 UPDATE SQL이 발생할 수 있기 때문에 일반적으로 @ManyToOne을 활용하는 구조를 선호


N:M 관계

11. @ManyToMany

Entity 간 N 대 M 관계를 표현

예를 들어

User N : M Food

한 명의 User가 여러 Food를 주문하고, 하나의 Food가 여러 User에게 주문될 수 있음

중간 테이블

N:M 관계를 DB에서 처리하기 위해 중간 테이블 사용

User
 ↓
Order
 ↑
Food

JPA @ManyToMany

@ManyToMany
@JoinTable(...)
private List<User> userList;

간단하게 사용할 수 있지만 중간 테이블을 직접 관리하기 어렵다는 단점 존재


12. 중간 Entity 사용

실무에서는 N:M 관계를 단순한 @ManyToMany로 처리하기보다 중간 Entity를 직접 만드는 방식을 사용할 수 있음

User 1 ─── N Order N ─── 1 Food
@Entity
public class Order {

    @ManyToOne
    private User user;

    @ManyToOne
    private Food food;
}

장점

중간 테이블에 추가 정보 저장 가능

예:

  • 주문 날짜
  • 수량
  • 상태
  • 가격

즉, 단순한 연결 테이블이 아니라 하나의 의미 있는 Entity로 관리 가능


Fetch Type

13. 지연 로딩과 즉시 로딩

JPA는 연관된 Entity를 언제 조회할지 설정할 수 있음

LAZY

지연 로딩

실제로 연관된 데이터가 필요한 시점에 조회

@ManyToOne(fetch = FetchType.LAZY)

EAGER

즉시 로딩

Entity를 조회할 때 연관된 Entity까지 즉시 조회

@ManyToOne(fetch = FetchType.EAGER)

기본 FetchType

관계기본값
@OneToManyLAZY
@ManyToManyLAZY
@ManyToOneEAGER
@OneToOneEAGER

일반적으로

연관된 데이터가 많을 수 있는 관계는 불필요한 조회를 줄이기 위해 지연 로딩 사용


14. 지연 로딩과 영속성 컨텍스트

지연 로딩은 실제 데이터가 필요한 시점에 조회하는 방식

User 조회
 ↓
User만 조회
 ↓
foodList 사용
 ↓
Food 조회

이때 지연 로딩된 Entity를 조회하려면 영속성 컨텍스트가 존재해야 함

즉, 일반적으로 트랜잭션이 유지되는 상태에서 지연 로딩된 데이터를 조회해야 함

Transaction
   ↓
Persistence Context
   ↓
Lazy Loading

핵심

Lazy Loading + Transaction

이 관계를 기억해둘 것


영속성 전이

15. Cascade

하나의 Entity에서 수행한 작업을 연관된 Entity에도 전파하는 기능

CascadeType.PERSIST

부모 Entity를 저장할 때 연관된 Entity도 함께 저장

@OneToMany(
    mappedBy = "user",
    cascade = CascadeType.PERSIST
)
User save()
   ↓
Food도 함께 save()

CascadeType.REMOVE

부모 Entity를 삭제할 때 연관된 Entity도 함께 삭제

cascade = CascadeType.REMOVE
User delete()
   ↓
Food도 함께 delete()

여러 옵션 사용

cascade = {
    CascadeType.PERSIST,
    CascadeType.REMOVE
}

고아 Entity

16. orphanRemoval

부모 Entity와의 연관관계가 끊어진 자식 Entity를 자동으로 삭제하는 기능

예를 들어

User
 ├─ Food 1
 ├─ Food 2
 └─ Food 3

여기서 Food 2를 User의 컬렉션에서 제거하면

User
 ├─ Food 1
 └─ Food 3

orphanRemoval = true 설정 시 DB에서도 Food 2가 삭제됨

@OneToMany(
    mappedBy = "user",
    orphanRemoval = true
)

Cascade REMOVE와 차이

Cascade REMOVE

부모 Entity 자체를 삭제할 때 자식 Entity도 삭제

orphanRemoval

부모와 자식의 연관관계가 끊어졌을 때 자식 Entity 삭제

주의

REMOVE, orphanRemoval 사용 시 다른 Entity에서 해당 객체를 참조하고 있는지 확인 필요


My Select Shop

17. 프로젝트 설계

이번 주에는 지금까지 학습한 내용을 활용하여 My Select Shop 프로젝트 구현

주요 기능

  • 네이버 상품 검색
  • 관심상품 등록
  • 관심상품 조회
  • 희망 최저가 설정
  • 회원가입 / 로그인
  • 회원별 관심상품 관리
  • 상품 정렬 및 페이징
  • 폴더 관리
  • 폴더별 관심상품 조회

18. 상품 검색

Naver Shopping Open API를 이용하여 상품 검색

검색어
 ↓
My Select Shop
 ↓ RestTemplate
Naver Shopping API
 ↓
상품 정보
 ↓
상품 목록 반환

상품 정보

  • 상품명
  • 상품 링크
  • 이미지
  • 최저가

19. 관심상품 API

상품 등록

POST /api/products

상품 정보를 DB에 저장

희망 최저가 수정

PUT /api/products/{id}

myprice만 수정

관심상품 조회

GET /api/products

회원이 저장한 관심상품 목록 조회

상품 Entity

주요 필드

id
title
image
link
lprice
myprice

Scheduler

20. Scheduler

일정한 시간 간격으로 특정 작업을 자동 실행하는 기능

My Select Shop에서는 관심상품의 최저가를 주기적으로 확인하는 작업에 활용

주요 애너테이션

@EnableScheduling

Scheduling 기능 활성화

@Scheduled(...)

특정 메서드를 주기적으로 실행

활용 예시

Scheduler
   ↓
Naver API 호출
   ↓
관심상품 최저가 조회
   ↓
DB 가격 업데이트

즉, 사용자가 직접 요청하지 않아도 정해진 시간에 서버에서 작업 수행


회원 기능

21. 회원가입과 로그인

My Select Shop에 회원 기능 추가

회원가입

  • username
  • password
  • 회원 정보 저장
  • 비밀번호 암호화

로그인

username
password
   ↓
인증
   ↓
JWT 발급
   ↓
Client 저장

기존에 학습한 Spring Security + JWT 인증 방식을 프로젝트에 적용


22. 회원별 데이터 관리

관심상품은 특정 회원에게 귀속되어야 함

User
 ↓
Product

상품 Entity에 User와의 연관관계 설정

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

핵심

로그인한 사용자의 상품만 조회할 수 있도록 사용자 식별 정보와 데이터의 연관관계 설정


페이징과 정렬

23. Pagination

데이터가 많아질 경우 모든 데이터를 한 번에 조회하지 않고 페이지 단위로 조회

Spring Data JPA에서는 Pageable 사용

Pageable pageable =
    PageRequest.of(page, size, sort);

주요 요소

  • page → 페이지 번호
  • size → 페이지당 데이터 개수
  • sort → 정렬 기준

반환 타입

Page<Product>

24. Sorting

상품 목록을 특정 기준으로 정렬

예:

  • ID
  • 상품명
  • 최저가

정렬 방향

  • ASC → 오름차순
  • DESC → 내림차순
Sort.Direction.ASC
Sort.Direction.DESC

Pagination + Sorting

Pageable
 ├─ page
 ├─ size
 └─ sort

Spring Data JPA의 Repository 메서드에 Pageable을 전달하여 페이징과 정렬을 함께 처리


Folder

25. 폴더 기능

관심상품을 효율적으로 관리하기 위한 폴더 기능 추가

Folder Entity

주요 정보

id
name
user

폴더와 회원은

Folder : User = N : 1

관계

회원 한 명이 여러 개의 폴더를 생성할 수 있음


26. 상품과 폴더의 관계

상품 하나가 여러 폴더에 포함될 수 있고, 하나의 폴더에도 여러 상품이 포함될 수 있음

Product : Folder = N : M

중간 Entity 사용

ProductFolder Entity 생성

Product 1 ─── N ProductFolder N ─── 1 Folder

ProductFolder

id
product
folder

N:M 관계를 중간 Entity를 통해 관리


27. 폴더 생성 및 조회

폴더 생성

POST /api/folders

회원이 원하는 폴더 생성

폴더 조회

GET /api/user-folder

로그인한 회원이 생성한 폴더만 조회

중요한 부분

회원 A가 생성한 폴더가 회원 B에게 노출되면 안 됨

따라서 로그인한 User와 Folder의 연관관계를 기준으로 조회


28. 관심상품에 폴더 추가

POST /api/products/{productId}/folder

상품과 폴더를 연결

처리 과정

상품 조회
 ↓
폴더 조회
 ↓
현재 로그인한 회원의 상품인지 확인
 ↓
현재 로그인한 회원의 폴더인지 확인
 ↓
중복 여부 확인
 ↓
ProductFolder 저장

중요한 검증

  • 상품의 소유자 확인
  • 폴더의 소유자 확인
  • 동일한 상품 + 폴더 중복 확인

단순히 ID만 받아 저장하는 것이 아니라 사용자 권한과 데이터 소유권까지 검증


29. 폴더별 관심상품 조회

회원은

  • 전체 관심상품
  • 특정 폴더의 관심상품

을 구분하여 조회 가능

GET /api/folders/{folderId}/products

조회 조건

User
 +
Folder ID
 +
Pageable

회원과 폴더를 함께 조건으로 사용하여 현재 로그인한 회원의 특정 폴더에 포함된 상품만 조회


이번 주 핵심 키워드

RestTemplate

  • 서버 간 통신
  • getForEntity()
  • postForEntity()
  • exchange()
  • RequestEntity
  • ResponseEntity
  • UriComponentsBuilder
  • Open API
  • JSON

JPA 연관관계

  • 단방향
  • 양방향
  • 연관관계의 주인
  • @JoinColumn
  • mappedBy
  • @OneToOne
  • @ManyToOne
  • @OneToMany
  • @ManyToMany
  • 중간 Entity

Fetch

  • FetchType
  • LAZY
  • EAGER
  • 지연 로딩
  • 즉시 로딩
  • 영속성 컨텍스트
  • Transaction

Cascade

  • 영속성 전이
  • CascadeType.PERSIST
  • CascadeType.REMOVE
  • orphanRemoval

My Select Shop

  • Open API
  • RestTemplate
  • 관심상품
  • Scheduler
  • 회원별 데이터
  • Pagination
  • Sorting
  • Folder
  • ProductFolder
  • N:M 관계

이번 주 핵심 흐름

RestTemplate
    ↓
외부 API 통신
    ↓
Naver Shopping API
    ↓
상품 데이터 조회
    ↓
JPA Entity 설계
    ↓
Entity 연관관계
    ↓
LAZY / EAGER
    ↓
Cascade / orphanRemoval
    ↓
My Select Shop 적용
    ↓
회원별 관심상품 관리
    ↓
페이징 / 정렬
    ↓
Folder / ProductFolder

단순히 JPA의 연관관계 애너테이션을 사용하는 것보다 왜 연관관계의 주인이 필요한지, 왜 중간 Entity를 사용하는지, 언제 지연 로딩을 사용하는지를 이해하는 것이 중요하다.

연관 관계 설정할 때 고려할 사항이 많아서 한 번 더 복습해야 할 것 같다.

0개의 댓글