Spring Boot 프로젝트 설계 — ERD와 첫 API 만들기

Do Hyun ·2026년 4월 24일

commerce-project

목록 보기
1/6

Spring Boot 프로젝트 설계 — ERD와 첫 API 만들기

프로젝트 개요

대용량 주문 처리 API 서버를 만들면서 Java 백엔드 기술을 정리합니다.

기술 스택

  • Spring Boot 3.2.x
  • Java 17
  • MySQL + JPA
  • Redis
  • Kafka
  • JWT 인증

ERD 설계

주문 시스템에 필요한 테이블 5개를 설계했습니다.

Member ─ 1:N ─ Order ─ 1:N ─ OrderItem ─ N:1 ─ Product
                 │
                1:1
                 │
             Delivery
테이블설명
members회원 정보
orders주문 정보
order_items주문 상품 목록
products상품 정보
deliveries배송 정보

order_items에 price를 따로 저장하는 이유

주문 당시 가격을 스냅샷으로 저장합니다.
상품 가격이 나중에 변경되더라도 주문 이력은 결제 시점 금액을 유지해야 하기 때문입니다.


Entity 설계

Member.java

@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Entity
public class Member {

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

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String password;

    private String userName;

    @OneToMany(mappedBy = "member")
    private List<Order> orderList;

    @CreationTimestamp
    private LocalDateTime createdAt;
}

Order.java

@Getter
@Table(name = "orders")
@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ORDER_ID")
    private Long id;

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

    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItemList = new ArrayList<>();

    @OneToOne(mappedBy = "order", fetch = FetchType.LAZY)
    private Delivery delivery;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    private Long orderPrice;

    @Enumerated(EnumType.STRING)
    private PaymentStatus paymentStatus;

    @Enumerated(EnumType.STRING)
    private OrderType orderType;

    private LocalDateTime orderedAt;
    private LocalDateTime paidAt;
}

OrderItem.java

@Getter
@Table(name = "ORDER_ITEM")
@Entity
public class OrderItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ORDER_ITEM_ID")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private Product product;

    private int quantity;
    private int orderPrice;
}

Product.java

@Getter
@Table(name = "product")
@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "PRODUCT_ID")
    private Long id;

    private String name;
    private int price;
    private int stockQuantity;
    private LocalDateTime createdAt;
}

Delivery.java

@Getter
@Table(name = "deliveries")
@Entity
public class Delivery {

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

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    private String address;

    @Enumerated(EnumType.STRING)
    private DeliveryEnum status;

    private LocalDateTime createdAt;
}

연관관계 정리

mappedBy란?

양방향 연관관계에서 연관관계의 주인을 지정합니다.
주인이 아닌 쪽은 외래키를 관리하지 않고 조회만 가능합니다.

// Order가 주인 — member_id 외래키 관리
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

// Member는 주인 아님 — 읽기만 가능
@OneToMany(mappedBy = "member")
private List<Order> orderList;

FetchType.LAZY를 쓰는 이유

연관된 Entity를 실제로 사용할 때 쿼리를 날립니다.
EAGER는 연관 Entity를 무조건 즉시 로딩해서 불필요한 쿼리가 발생할 수 있습니다.


회원가입 API

MemberRepository.java

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
    boolean existsMemberByEmail(String email);
}

MemberService.java

public Long signUp(SignUpRequest signUpRequest) {
    // 이메일 중복 체크
    boolean isAlreadyMember = memberRepository.existsMemberByEmail(signUpRequest.getEmail());
    if (isAlreadyMember) {
        throw new IllegalArgumentException("이미 가입된 회원 이메일입니다.");
    }

    // 비밀번호 암호화
    String encodePassword = passwordEncoder.encode(signUpRequest.getPassword());

    // 빌더 패턴으로 객체 생성
    Member member = Member.builder()
            .email(signUpRequest.getEmail())
            .password(encodePassword)
            .userName(signUpRequest.getUserName())
            .build();

    memberRepository.save(member);
    return member.getId();
}

MemberController.java

@RequiredArgsConstructor
@RestController
@RequestMapping("/member")
public class MemberController {

    private final MemberService memberService;

    @PostMapping("/signUp")
    public ResponseEntity<Long> signUp(@RequestBody SignUpRequest signUpRequest) {
        memberService.signUp(signUpRequest);
        return ResponseEntity.ok().build();
    }
}

API 호출 테스트

POST http://localhost:8080/member/signUp
Content-Type: application/json

{
    "email": "test@test.com",
    "password": "1234",
    "userName": "홍길동"
}

오늘 배운 것

  • JPA Entity 설계 및 연관관계 매핑 (1:N, N:1, 1:1)
  • mappedBy로 연관관계 주인 지정
  • @Builder + @NoArgsConstructor 함께 쓰는 법
  • Entity에 @Setter를 쓰면 안 되는 이유
  • 이메일 중복체크 + 비밀번호 암호화 적용

profile
우당탕탕

0개의 댓글