[Spring Boot] 쇼핑몰 프로젝트 (7) - 연관 관계 매핑

YulHee Kim·2021년 12월 8일
1
post-thumbnail

✏️ 연관 관계 매핑 종류

총 4가지의 매핑 관계가 있습니다.

  • 일대일(1:1) : @OneToOne
  • 일대다(1:N) : @OneToMany
  • 다대일(N:1) : @ManyToOne
  • 다대다(N:M) : @ManyToMany

예를들어 쇼핑몰에서 회원들은 각자 자신의 장바구니를 하나 갖고 있고,
장바구니 입장에서도 자신과 매핑되는 한명의 회원을 갖습니다 => 일대일 매핑
하나의 장바구니에는 여러 개의 상품이 들어갈 수 있습니다 => 일대다 매핑

✏️ 일대일 단방향 매핑하기

장바구니(Cart) 엔티티를 만들고 회원 엔티티와 연관 관계 매핑을 설정하겠습니다.

장바구니 엔티티 설계

@Entity
@Table(name = "cart")
@Getter @Setter
@ToString
public class Cart {

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

    @OneToOne
    @JoinColumn(name = "member_id")
    private Member member;
}

장바구니 엔티티가 일방적으로 회원 엔티티를 참조하는 일대일 단방향 매핑 형태입니다.
cart 테이블은 member_id 컬럼을 외래키로 갖습니다.

CartRepository 인터페이스 생성 후 CartTest를 작서해보겠습니다.

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class CartTest {

    @Autowired
    CartRepository cartRepository;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    PasswordEncoder passwordEncoder;

    @PersistenceContext
    EntityManager em;

    public Member createMember(){
        MemberFormDto memberFormDto = new MemberFormDto();
        memberFormDto.setEmail("test@email.com");
        memberFormDto.setName("홍길동");
        memberFormDto.setAddress("서울시 마포구 합정동");
        memberFormDto.setPassword("1234");
        return Member.createMember(memberFormDto, passwordEncoder);
    }

    @Test
    @DisplayName("장바구니 회원 엔티티 매핑 조회 테스트")
    public void findCartAndMemberTest(){
        Member member = createMember();
        memberRepository.save(member);
        Cart cart = new Cart();
        cart.setMember(member);
        cartRepository.save(cart);

        em.flush();
        em.clear();

        Cart savedCart = cartRepository.findById(cart.getId())
                .orElseThrow(EntityNotFoundException::new);
        assertEquals(savedCart.getMember().getId(), member.getId());
    }

}

JPA는 영속성 컨텍스트에 데이터를 저장 후 트랜잭션이 끝날 때 flush()를 호출하여 데이터베이스에 반영합니다.
JPA는 영속성 컨텍스트로부터 엔티티를 조회 후 영속성 컨텍스트에 엔티티가 없을 경우 데이터베이스를 조회합니다. 실제 데이터베이스에서 장바구니 엔티티를 가지고 올 때 회원 엔티티도 가지고오는지 보기 위해서 영속성 컨텍스트를 비워줍니다.

엔티티를 조회할 때 해당 엔티티와 매핑된 엔티티도 한 번에 조회하는 것을 '즉시 로딩'이라고 합니다. 일대일, 다대일로 매핑할 경우 즉시 로딩을 기본 Fetch 전략으로 설정합니다.

✏️ 다대일 단방향 매핑하기

장바구니에는 여러 개의 상품들이 들어갈 수 있습니다. 또한 같은 상품을 여러 개 주문할 수도 있습니다.

장바구니 아이템 엔티티 설계하기

CartItem

@Entity
@Getter @Setter
@Table(name="cart_item")
public class CartItem {
    
    @Id
    @GeneratedValue
    @Column(name = "cart_item_id")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name="cart_id")
    private Cart cart;
    
    @ManyToOne
    @JoinColumn(name = "item_id")
    private Item item;
    
    private int count;
}

하나의 장바구니에는 여러 개의 상품을 담을 수 있고, 하나의 상품은 여러 장바구니의 장바구니 상품으로 담길 수 있으므로 @ManyToOne 어노테이션을 이용하여 다대일 관계로 매핑합니다.

✏️ 다대일/일대다 양방향 매핑하기

양방향 매핑이란 단방향 매핑이 2개 있다고 생각하면 됩니다.
주문과 주문 상품 매핑을 통해 양방향 매핑을 알아보겠습니다.

주문 엔티티를 먼저 설계하겠습니다.

주문 도메인 엔티티 설계하기

OrderStatus

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

    @Id @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    private LocalDateTime orderDate; //주문일

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus; //주문상태

    private LocalDateTime regTime;
    private LocalDateTime updateTime;
    
}

한 명의 회원은 여러 번 주문할 수 있으므로 다대일 단방향 매핑입니다.

OrderItem을 설계해보겠습니다.

@Entity
@Getter @Setter
public class OrderItem {
    
    @Id @GeneratedValue
    @Column(name = "order_item_id")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "item_id")
    private Order order;
    
    private int orderPrice; //주문 가격
    
    private int count; //수량
    
    private LocalDateTime regTime;
    
    private LocalDateTime updateTime;
}

하나의 상품은 여러 주문 상품으로 들어갈 수 있고, 한 번의 주문에 여러 개의 상품을 주문할 수 있으므로 다대일 단방향 매핑을 설정합니다.

엔티티를 양방향 연관 관계로 설정하면 객체의 참조는 둘인데 외래키는 하나이므로 둘 중 누가 외래키를 관리할지 정해야합니다.

  • 연관 관계의 주인은 외래키가 있는 곳으로 설정
  • 연관 관계의 주인이 외래키를 관리(등록, 수정, 삭제)
  • 주인이 아닌 쪽은 연관 관계 매핑 시 mappedBy 속성의 값으로 연관 관계의 주인을 설정
  • 주인이 아닌 쪽은 읽기만 가능

Order 엔티티에 OrderItem과 연관 관계 매핑을 추가하겠습니다.

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

    @Id @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    private LocalDateTime orderDate; //주문일

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus; //주문상태
    
    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems = new ArrayList<>();

    private LocalDateTime regTime;
    private LocalDateTime updateTime;

}

주문 상품 엔티티와 일대다 매핑을 합니다. 외래키(order_id)가 order_item 테이블에 있으므로 연관 관계의 주인은 OrderItem 엔티티입니다. Order 엔티티가 주인이 아니므로 "mappedBy" 속성으로 연관 관계의 주인을 설정합니다.

다음엔 영속성 전이, 지연 로딩에 대해 더 공부해보겠습니다 ㅇㅂㅇ

profile
백엔드 개발자

0개의 댓글