본문 바로가기
Back-End/🌱 Spring Boot (java)

[Spring JPA] 엔티티(연관 테이블) 생성하고 일대일,일대다 연관관계(1:1 1:n) 설정하기

by 코딩하는 동현😎 2023. 1. 31.

JPA 세팅에 대해선 이전 포스트를 보고 참고하세요!

build.gradle (스프링 2.7.8 기준)

getter,setter를 자동으로 생성해주는 lombok 디펜던시를 사용했습니다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	// implementation 'org.springframework.boot:spring-boot-devtools'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

1. 실습할 기준 도메인 모델

회원, 주문, 상품의 관계 : 회원은 여러 상품을 주문할 수 있습니다(주문과 일대다 관계). 그리고 한 번 주문할 때 여러 상품을 선택할 수 있으므로 주문과 상품(물품)은 다대다 관계입니다.

하지만 이런 다대다 관계는 관계형 데이터베이스는 물론이고 엔티티에서도 실무에서 거의 사용하지 않습니다.

따라서 그림처럼 주문상품이라는 엔티티를 추가해서 다대다 관계를 일대다, 다대일 관계로 풀어냈습니다.

 

상품 분류 : 상품은 도서, 음반, 영화로 구분되는데 상품이라는 공통 속성을 사용하므로 상속 구조로 표현했습니다.(상품을 추상 클래스로 이용)

 

2. 엔티티 모델 구성하기 (자바 클래스) - 실습 예시

  1. 회원(Member): 이름과 임베디드 타입인 주소( Address ), 그리고 주문( orders ) 리스트를 가진다.
  2. 주문(Order): 한 번 주문시 여러 상품을 주문할 수 있으므로 주문과 주문상품( OrderItem )은 일대다 관계다. 주문은 상품을 주문한 회원과 배송 정보, 주문 날짜, 주문 상태( status )를 가지고 있다.
  3. 주문 상태는 열거형을 사용했는데 주문( ORDER ), 취소( CANCEL )을 표현할 수 있다.
  4. 주문상품(OrderItem): 주문한 상품 정보와 주문 금액( orderPrice ), 주문 수량( count ) 정보를 가지고 있다. (보통 OrderLine , LineItem 으로 많이 표현한다.)
  5. 상품(Item): 이름, 가격, 재고수량( stockQuantity )을 가지고 있다. 상품을 주문하면 재고수량이 줄어든다. 상품의 종류로는 도서, 음반, 영화가 있는데 각각은 사용하는 속성이 조금씩 다르다.
  6. 배송(Delivery): 주문시 하나의 배송 정보를 생성한다. 주문과 배송은 일대일 관계다.
  7. 카테고리(Category): 상품과 다대다 관계를 맺는다. parent , child 로 부모, 자식 카테고리를 연결한다.
  8. 주소(Address): 값 타입(임베디드 타입)이다. 회원과 배송(Delivery)에서 사용한다.

 

3. 실습의 RDBMS 테이블 구성하기

위와 같은 테이블을 만들 것이고, 이번 실습에서는 Member, Order, Deliver, OrderItem 엔티티만 만들겠습니다.


자바 객체를 DB스키마와 연동하기

그 객체에 아래와 같은 몇몇 어노테이션을 추가해주면 완성입니다.

  • @Entity: JPA를 사용할 클래스를 명시하며, 테이블과 매핑하는 역할을 합니다.
  • @Id : 기본키(Primary Key)라는 것을 명시하는 역할을 합니다.
  • @GeneratedValue: 키값의 자동생성 전략을 설정할 수 있습니다. (default: GenerationType.AUTO)
    • 이 실습에서는 MySQL에서 전략을 사용하므로 위 어노테이션을 사용합니다.
  • @Column: 컬럼의 속성값을 추가로 부여 할 수 있습니다.

Member 테이블 생성 후 일대다 관계

 

src/main/java/jpabook/jpastore/domain/Member.java 

package jpabook.jpastore.domain;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Member {
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")
    //  order가 주인. Order객체의 members 필드를 이용
    private List<Order> orders = new ArrayList<>();
}

각 기능별 설명

 

1. 아래의 임의로 만든 Address 객체를 사용시 @Embeded 를 작성해줍니다. (그 객체에는 @Embeddable를 추가해줍니다.)

임의로 만든 Address 객체 참고

package jpabook.jpastore.domain;
import javax.persistence.Embeddable;
import lombok.Getter;

@Embeddable
@Getter
public class Address {
    private String city;
    private String street;
    private String zipcode;
    protected Address(){}
    public void setAddress(String city, String street, String zipcode){
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

2. Order과 일대다 관계

order이 '다'에 해당되므로 Member 객체에서는 order를 여러개가지므로 orders 리스트를 만들어주고 @OneToMany 어노테이션을 추가해줍니다(테이블에는 반영X, 자바 객체에만 반영)

 

order이 '다'에 해당되므로 매팽의 주인 order입니다. 그러므로 order과 매핑해야되므로 @OneToMany(mappedBy = "member")로 추가해줍니다.

 

Order를 연결해줘야되는데 member인 이유 : Order객체의 members 필드를 이용해야하므로 입니다. Order의 member 필드 또한 테이블에 반영이 안되고, 오로지 연관을 위해서 존재하는 객체입니다. (아래에 order 객체에 대한 설명이 나옵니다.)


SQL 쿼리 결과

Member객체에서 별도 이름 설정을 안하면 데이블 이름은 전부 소문자로, 캐멀케이스로 이름이 설정됩니다.

address객체를 연결했으므로 address객체의 각 필드가 속성으로 상속됩니다.

    create table member (
       member_id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        name varchar(255),
        primary key (member_id)
    ) engine=InnoDB

Order 테이블 생성 후  Member과 다대일 관계 , Deilver과 일대일 관계 등등

jpaBooks/jpastore/src/main/java/jpabook/jpastore/domain/Order.java

package jpabook.jpastore.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
import lombok.*;

@Entity
@Getter
@Setter
// 테이블 이름 임의로 설정하고 싶으면 아래와 같이 하시면 됩니다.
@Table(name = "orders")
public class Order {
    @Id @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id") //FK = Member테이블의 member_id
    private Member member;

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

    @OneToOne
    @JoinColumn(name = "deilvery_id")
    private Delivery delivery;

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // 주문 상태 [ORDER, CANCLE]
}

각 기능별 설명

 

1. Member를 외래키를 이용해 연관시키기 (Foreign Key)

Member과의 다대일 관계이므로 @ManyToOne 어노테이션을 이용해주고, @JoinColumn(name = "member_id")을 이용해서 Member의 member_id 과 연결하고 member_id이름의 외래키를 만들어줍니다.

 

결과 : 

    alter table orders 
       add constraint FKpktxwhj3x9m4gth5ff6bkqgeb 
       foreign key (member_id) 
       references member (member_id)

2. delivery와 일대일 관계

일대다 같은 경우에는 '다'부분이 매핑의 주인이었으나, 일대일 관계 같은 경우는 비즈니스에서 어떤식으로 접근하는지 생각 해야합니다.

주문목록 -> 배송지로 접근을 하지, 배송지 -> 주문목록으로 이동하지 않습니다.

그러므로 주문목록, Order가 주인이고, Foreign Key를 가지고, delivery는 order의 delivery 필드를 매핑합니다.

@OneToOne @JoinColumn(name = "deilvery_id") private Delivery delivery; 를 이용해서 delivery_id를 이용해서 외래키를 적용시켜주고 연결 시켜줍니다.

 

jpaBooks/jpastore/src/main/java/jpabook/jpastore/domain/Delivery.java

역시 Delivery 엔티티에서는 order 칼럼이 오더 객체와 조인돼 합쳐지는것으므로 @JoinColumn으로 연관시켜주고, order의 delivery 필드를 매핑합니다.

package jpabook.jpastore.domain;
import javax.persistence.*;

import lombok.Getter;
import lombok.Setter;

@Entity @Getter @Setter
public class Delivery {
    @Id @GeneratedValue
    @Column(name = "delivery_id")
    private Long id;

    @OneToOne(mappedBy = "delivery")
    @JoinColumn
    private Order order;

    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus status; // [READY , COMP] 준비, 완료

}

위 처럼 열거형(Enum) 속성을 이용하고 싶으면 DeliverStatus처럼 enum형 객체를 만들고, @Enumerated 어노테이션으로 적용시켜줍니다.

타입으로는 문자열로 저장하거나 순서(숫자)로 저장하는 방법이 있지만, 후자는 열거 데이터에 추가 변경시 문제가 생기므로 EnumType.STRING를 많이 사용합니다.


3. OrderItem과 일대다 관계

order과 item을 연결하려면 다대다 관계가 되므로 order_Item이라는 중간테이블를 생성하고 연결해줍니다.

일대다 연결하는 방법은 이미 설명했지만, 참고하고 싶은 사람들이 있을거 같아서 작성한 코드를 보여드리겠습니다.

 

jpaBooks/jpastore/src/main/java/jpabook/jpastore/domain/OrderItem.java

package jpabook.jpastore.domain;
import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class OrderItem {
    @Id @GeneratedValue
    @Column(name = "order_item_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;

    private int orderPrice;
    private int count;
}

연관 관계 SQL문 결과 (참고)

    alter table order_item 
       add constraint FKt4dc2r9nbvbujrljv3e23iibt 
       foreign key (order_id) 
       references orders (order_id)
Hibernate: 
    
    alter table orders 
       add constraint FK5bboc0sakyohkbd380hgp18g8 
       foreign key (deilvery_id) 
       references delivery (delivery_id)
Hibernate: 
    
    alter table orders 
       add constraint FKpktxwhj3x9m4gth5ff6bkqgeb 
       foreign key (member_id) 
       references member (member_id)

DDL create문 결과 (참고)

    create table delivery (
       delivery_id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        status varchar(255),
        primary key (delivery_id)
    ) engine=InnoDB
Hibernate: 
   
    create table order_item (
       order_item_id bigint not null,
        count integer not null,
        order_price integer not null,
        order_id bigint,
        primary key (order_item_id)
    ) engine=InnoDB
Hibernate: 
    
    create table orders (
       order_id bigint not null,
        order_date datetime(6),
        status varchar(255),
        deilvery_id bigint,
        member_id bigint,
        primary key (order_id)
    ) engine=InnoDB
반응형

댓글