[Backend/Spring] Spring Boot - JPA 연관관계

2026. 3. 29. 18:27·Backend/Spring

연관관계

회원과 상품의 관계를 생각해보자.

회원 1명 → 상품 여러 개 등록 가능
상품 1개 → 회원 1명에게 속함

이런 관계를 DB에서는 외래키(Foreign Key)로 표현하고, JPA에서는 @ManyToOne, @OneToMany 로 표현한다.

Member (1) ←→ Item (N)
Item 테이블에 member_id 컬럼이 생김 (외래키)

상품 엔티티 만들기

com.seongmo.myshop 하위에 item 패키지를 만들고 아래 파일들을 생성하자.

com.seongmo.myshop
├── member
└── item
    ├── Item.java
    ├── ItemStatus.java
    ├── ItemController.java
    ├── ItemService.java
    ├── ItemRepository.java
    └── dto
        ├── ItemCreateRequest.java
        └── ItemResponse.java

ItemStatus.java 만들기

package com.seongmo.myshop.item;

public enum ItemStatus {
    SELLING,    // 판매중
    RESERVED,   // 예약중
    SOLD_OUT    // 판매완료
}

상품 상태를 enum으로 관리한다.

Item.java 만들기

package com.seongmo.myshop.item;

import com.seongmo.myshop.member.Member;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "item")
@Getter
@NoArgsConstructor
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String description;

    @Column(nullable = false)
    private int price;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private ItemStatus status;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member seller;

    public Item(String title, String description, int price, Member seller) {
        this.title = title;
        this.description = description;
        this.price = price;
        this.status = ItemStatus.SELLING;
        this.seller = seller;
    }
}
  • @ManyToONe 은 "상품 여러 개가 회원 한 명에게 속한다"는 관계를 선언한다.
  • fetch = FetchType.LAZY 는 상품을 조회할 때 회원 정보를 즉시 가져오지 않고 실제로 필요할 때만 가져오도록 한다. 연관관계에서는 기본적으로 LAZY 를 사용하는 것이 권장된다.
  • @JoinColumn(name = "member_id") 는 DB에 생성될 외래키 컬럼 이름을 지정한다.
  • @Enumerated(EnumType.STRING) 은 enum 값을 DB에 문자열로 저장한다. 숫자(0,1,2)로 지정하면 나중에 enum 순서가 바뀔 때 데이터가 꼬일 수 있어서 STRING을 사용한다.

ItemCreateRequest.java 만들기

package com.seongmo.myshop.item.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ItemCreateRequest {
    private String title;
    private String description;
    private int price;
    private Long memberId;
}

ItemResponse.java 만들기

package com.seongmo.myshop.item.dto;

import com.seongmo.myshop.item.Item;
import lombok.Getter;

@Getter
public class ItemResponse {
    private Long id;
    private String title;
    private String description;
    private int price;
    private String status;
    private String sellerNickname;

    public ItemResponse(Item item) {
        this.id = item.getId();
        this.title = item.getTitle();
        this.description = item.getDescription();
        this.price = item.getPrice();
        this.status = item.getStatus().name();
        this.sellerNickname = item.getSeller().getNickname();
    }
}

ItemRepository.java 만들기

package com.seongmo.myshop.item;

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface ItemRepository extends JpaRepository<Item, Long> {
    List<Item> findBySellerId(Long sellerId);
}
  • findBySellerId 는 특정 회원이 등록한 상품 목록을 조회하는 메서드다. JPA가 메서드 이름을 보고 자동으로 쿼리를 만들어준다.

ItemService.java 만들기

package com.seongmo.myshop.item;

import com.seongmo.myshop.item.dto.ItemCreateRequest;
import com.seongmo.myshop.item.dto.ItemResponse;
import com.seongmo.myshop.member.Member;
import com.seongmo.myshop.member.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class ItemService {

    private final ItemRepository itemRepository;
    private final MemberRepository memberRepository;

    @Transactional
    public ItemResponse createItem(ItemCreateRequest request) {
        Member seller = memberRepository.findById(request.getMemberId())
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다."));

        Item item = new Item(
                request.getTitle(),
                request.getDescription(),
                request.getPrice(),
                seller
        );

        Item savedItem = itemRepository.save(item);
        return new ItemResponse(savedItem);
    }

    public ItemResponse getItem(Long id) {
        Item item = itemRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 상품입니다."));
        return new ItemResponse(item);
    }

    public List<ItemResponse> getItemsBySeller(Long memberId) {
        return itemRepository.findBySellerId(memberId)
                .stream()
                .map(ItemResponse::new)
                .collect(Collectors.toList());
    }
}

ItemController.java 만들기

package com.seongmo.myshop.item;

import com.seongmo.myshop.item.dto.ItemCreateRequest;
import com.seongmo.myshop.item.dto.ItemResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/items")
public class ItemController {

    private final ItemService itemService;

    @PostMapping
    public ResponseEntity<ItemResponse> createItem(@RequestBody ItemCreateRequest request) {
        return ResponseEntity.ok(itemService.createItem(request));
    }

    @GetMapping("/{id}")
    public ResponseEntity<ItemResponse> getItem(@PathVariable Long id) {
        return ResponseEntity.ok(itemService.getItem(id));
    }

    @GetMapping("/seller/{memberId}")
    public ResponseEntity<List<ItemResponse>> getItemsBySeller(@PathVariable Long memberId) {
        return ResponseEntity.ok(itemService.getItemsBySeller(memberId));
    }
}

실행 및 확인

Intellij에서 실행해보면 item 테이블이 자동으로 생성되는 것을 확인할 수 있다.

Postman으로 상품 등록 요청을 해보자.

또한, DBeaver에서도 item 테이블이 잘 생성된 것을 확인할 수 있다.

특정 회원의 전체 판매 상품을 조회할 수도 있다

 

저작자표시 (새창열림)

'Backend > Spring' 카테고리의 다른 글

[Backend/Spring] Spring Boot - 예외 처리  (0) 2026.03.29
[Backend/Spring] Spring Boot - @OneToMany 와 N+1 문제  (0) 2026.03.29
[Backend/Spring] Spring Boot - JPA와 MySQP 연동  (0) 2026.03.29
[Backend/Spring] Spring Boot - MVC 계층 구조와 API  (0) 2026.03.29
[Backend/Spring] Spring Boot - IoC/DI/Bean 개념과 MySQL 연결  (0) 2026.03.29
'Backend/Spring' 카테고리의 다른 글
  • [Backend/Spring] Spring Boot - 예외 처리
  • [Backend/Spring] Spring Boot - @OneToMany 와 N+1 문제
  • [Backend/Spring] Spring Boot - JPA와 MySQP 연동
  • [Backend/Spring] Spring Boot - MVC 계층 구조와 API
Study with Me!
Study with Me!
Study with Me!
  • Study with Me!
    Seongmo
    Study with Me!
  • 전체
    오늘
    어제
    • Computer (147) N
      • Computer Science (61)
        • Data Structure (51)
        • Algorithm (6)
        • 선형대수 with C++ (4)
      • Backend (11) N
        • 백엔드 취업을 위해.. (1)
        • Spring (10) N
        • Database (0)
        • Testing (0)
        • Infra & DevOps (0)
      • Arm Architecture (1)
        • Register (0)
        • Assembly Instruction (1)
      • Linux (32)
        • Linux Kernel (4)
        • 라이브러리 함수 구현하기 (0)
        • 쉘, 쉘 명령어 구현하기 (15)
        • Ubuntu (13)
      • Cloud Infrastructure (8)
        • Kubernetes (7)
        • OpenStack Magnum (1)
      • AWS (3)
      • Baekjoon (18)
      • Tools (6)
        • Git & Github (5)
        • Vim (1)
      • 개발 환경 (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    STL
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Study with Me!
[Backend/Spring] Spring Boot - JPA 연관관계
상단으로

티스토리툴바