총 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;
}
하나의 상품은 여러 주문 상품으로 들어갈 수 있고, 한 번의 주문에 여러 개의 상품을 주문할 수 있으므로 다대일 단방향 매핑을 설정합니다.
엔티티를 양방향 연관 관계로 설정하면 객체의 참조는 둘인데 외래키는 하나이므로 둘 중 누가 외래키를 관리할지 정해야합니다.
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" 속성으로 연관 관계의 주인을 설정합니다.
다음엔 영속성 전이, 지연 로딩에 대해 더 공부해보겠습니다 ㅇㅂㅇ