본문 바로가기

Backend/Spring

엔티티 매핑과 연관 관계 매핑

엔티티 매핑

- 객체와 테이블 매핑: @Entity, @Table
- 필드와 컬럼 매핑: @Column
- 기본 키 매핑: @Id
- 연관 관계 매핑: @ManyToOne, @JoinColumn
 

예제 

요구사항
- 회원은 상품을 주문할 수 있다.
- 주문 시 여러 종류의 상품을 선택할 수 있다.
 
테이블 설계

 
 
엔티티 설계와 매핑

 

package jpabook.jpashop.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

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

	private String name;
	private String city;
	private String street;
	private String zipcode;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getStreet() {
		return street;
	}

	public void setStreet(String street) {
		this.street = street;
	}

	public String getZipcode() {
		return zipcode;
	}

	public void setZipcode(String zipcode) {
		this.zipcode = zipcode;
	}
}
package jpabook.jpashop.domain;

public enum OrderStatus {
	ORDER, CANCEL
}
package jpabook.jpashop.domain;

import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.time.LocalDateTime;

@Entity
@Table(name = "ORDERS")
public class Order {
	@Id @GeneratedValue
	@Column(name = "ORDER_ID")
	private Long id;

	@Column(name = "MEMBER_ID")
	private Long memberId;

	private LocalDateTime orderDate;
		
	@Enumerated(EnumType.STRING)
	private OrderStatus status;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Long getMemberId() {
		return memberid;
	}

	public void setMemberId(Long memberId) {
		this.memberId = memberId;
	}

	public LocalDateTime getOrderDate() {
		return orderDate;
	}

	public void setOrderDate(LocalDateTime orderDate) {
		this.orderDate = orderDate;
	}

	public OrderStatus getStatus() {
		return status;
	}

	public void setStatus(OrderStatus status) {
		this.status= status;
	}
}
package jpabook.jpashop.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

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

	@Column(name = "ORDER_ID")
	private Long orderId;

	@Column(name = "ITEM_ID")
	private Long itemId;
		
	private int orderPrice;
	private int count;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Long getOrderId() {
		return orderId;
	}

	public void setOrderId(Long orderId) {
			this.orderId = orderId;
	}

	public Long getItemId() {
		return itemId;
	}

	public void setItemId(Long itemId) {
		this.itemId = itemId;
	}

	public int getOrderPrice() {
		return orderPrice;
	}

	public void setOrderPrice(int orderPrice) {
		this.orderPrice = orderPrice;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}
}
package jpabook.jpashop.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Item {
	@Id @GeneratedValue
	@Column(name = "ITEM_ID")
	private Long id;

	private String name;
	private int price;
	private int stockQuantity;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	public int getStockQuantity() {
		return stockQuantity;
	}

	public void setStockQuantity() {
		this.stockQuantity = stockQuantity;
    }
}

위 설계들을 바탕으로 코드를 작성해 봤다.
하지만, 위 코드들은 수정이 조금 필요한 코드이다.
객체 지향스러운 설계 및 구현이 아니다.
 
이런 설계를 데이터 중심 설계라고 한다.
객체 설계를 테이블 설계에 맞춘 방식이다.
객체 그래프 탐색이 불가능하고, 참조가 없어서 UML도 잘못되어 있다.
 
이를 해결하기 위해서는 연관 관계 매핑을 알아야 한다.
 

연관 관계 매핑

양방향 연관 관계단방향 연관 관계에 대해서 알아보자.
 
아래는 단방향 연관 관계를 나타낸 그림이다.

아래는 양방향 연관 관계를 나타내는 그림이다.

테이블 연관 관계의 경우, 단방향 연관 관계에서와 동일하다.
위 테이블의 경우 TEAM_ID를 조인시키면 서로 접근할 수 있게 된다.
 
다만 객체 양방향 연관 관계를 위해서, Team에서 member 접근을 하기 위해 List를 추가적으로 넣어주었다.
 
객체 연관 관계는 2개이고, 테이블 연관 관계는 1개이다.
객체 연관 관계의 경우, 회원 -> 팀 연관 관계 1개(단방향), 팀 -> 회원 연관 관계 1개(단방향), 총 2개 연관 관계로 이루어져 있다.
테이블 연관 관계는 회원 <-> 팀의 연관 관계 1개(양방향), 총 1개 연관 관계로 이루어져 있다.
객체의 양방향 관계는 서로 다른 단방향 관계 2개이다.
테이블은 외래 키 하나로 두 테이블의 연관 관계를 관리하게 된다.
위 예에서는 MEMBER.TEAM_ID 외래 키 하나로 양방향 연관 관계를 관리한다.
 
이제 그러면 연관 관계의 주인이 어디에 있는지를 알아봐야 한다.
연관 관계의 주인 외래 키가 있는 곳에 있다.
그래서, 이 예에서는 Member.team이 연관 관계의 주인이 된다.
 
연관 관계의 주인만이 외래 키를 관리, 등록, 수정할 수 있고, 주인이 아닌 쪽은 읽기만 가능하다.
주인이 아니면 mappedBy 속성으로 주인을 지정한다.
주인은 mappedBy 속성을 사용하지 않는다.
 
이전 예제에서 생겼던 문제를 연관 관계 매핑을 통해 해결해 보자.
 
다음은 테이블 구조인데, 이전과 동일하다.

 
다음은 객체 구조이다.

package jpabook.jpashop.domain;

import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.time.LocalDateTime;

@Entity
@Table(name = "ORDERS")
public class Order {
	@Id @GeneratedValue
	@Column(name = "ORDER_ID")
	private Long id;

//	  @Column(name = "MEMBER_ID")
//	  private Long memberId;

	@ManyToOne
	@JoinColumn(name = "MEMBER_ID")
	private Member member;
		
	@OneToMany(mappedBy = "order")
	private List<OrderItem> orderItems = new ArrayList<>();

	private LocalDateTime orderDate;
		
	@Enumerated(EnumType.STRING)
	private OrderStatus status;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Member getMember() {
		return member;
	}

	public void setMember(Member member) {
		this.member = member;
	}

	public LocalDateTime getOrderDate() {
		return orderDate;
	}

	public void setOrderDate(LocalDateTime orderDate) {
		this.orderDate = orderDate;
	}

	public OrderStatus getStatus() {
		return status;
	}

	public void setStatus(OrderStatus status) {
		this.status= status;
	}
}
package jpabook.jpashop.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

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

//	  @Column(name = "ORDER_ID")
//	  private Long orderId;
	
	@ManyToOne
	@JoinColumn(name = "ORDER_ID")
	private Order order;

//	  @Column(name = "ITEM_ID")
//	  private Long itemId;
		
	@ManyToOne
	@JoinColumn(name = "ITEM_ID")
	private Item item;
		
	private int orderPrice;
	private int count;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Order getOrder() {
		return order;
	}

	public void setOrder(Order order) {
		this.order = order;
	}

	public Item getItem() {
		return item;
   	}

	public void setItem(Item item) {
		this.item = item;
	}

	public int getOrderPrice() {
		return orderPrice;
	}

	public void setOrderPrice(int orderPrice) {
		this.orderPrice = orderPrice;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}
}
package jpabook.jpashop.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

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

	private String name;
	private String city;
	private String street;
	private String zipcode;
	
	@OneToMany(mappedBy = "member")
	private List<Order> orders = new ArrayList<>();

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getStreet() {
		return street;
	}

	public void setStreet(String street) {
		this.street = street;
	}

	public String getZipcode() {
		return zipcode;
	}

	public void setZipcode(String zipcode) {
		this.zipcode = zipcode;
	}
}
package jpabook.jpashop.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Item {
	@Id @GeneratedValue
	@Column(name = "ITEM_ID")
	private Long id;

	private String name;
	private int price;
	private int stockQuantity;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	public int getStockQuantity() {
		return stockQuantity;
	}

	public void setStockQuantity() {
		this.stockQuantity = stockQuantity;
    }
}

 
단방향 매핑만으로도 이미 연관 관계 매핑은 완료되었는데, 양방향 매핑은 반대 방향으로 조회할 수 있는 기능이 추가된 것뿐이다.
그래서 단방향 매핑을 잘해두고 양방향은 필요한 경우 추가하면 된다.
 
아 그리고, 양방향 매핑 시 연관 관계의 주인에 값을 입력해야 한다는 점을 잊지 말자.
이를 위해서 다음과 같이 연관 관계 편의 메서드를 작성하기도 한다.

public void changeTeam(Team team) {
	this.team = team;
	team.getMembers().add(this);
}

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

고급 매핑  (0) 2023.08.16
다양한 연관 관계 매핑  (0) 2023.08.16
영속성 컨텍스트  (0) 2023.08.09
빈 스코프  (1) 2023.08.03
빈 생명주기 콜백  (0) 2023.08.03