백엔드/Spring

스프링일기 14 - JPA(5) JOIN

들쮜 2023. 6. 19. 15:22

SQL에서는 FK를 이용해서 다른 테이블을 가져와서 사용할 수 있습니다.

 

가장 대표적인 예가 게시판과 댓글입니다.

 

게시판은 게시판으로서 여러 정보들을 가지고 있는 테이블이고,

 

댓글은 댓글로서 또한 여러 정보들을 가지고 있는 테이블입니다.

 

우리가 특정 게시판에 쓰여진 댓글만 가지고 오고 싶을 때는, 댓글에 게시판 ID컬럼을 하나 만들어 관리를 해주게 된다.

 

게시판 ID1 에 담겨진 댓글의 ID 1, 2, 3, 4가 있다고 하면

 

ID1, 2, 3, 4에 게시판 아이디 1을 넣어주는 것입니다.

 

게시판 하나에 여러 댓글들이 들어가게 되고, 이 때, 

 

객체 안에 다른 연관관계가 있는 객체를 필드에 넣어줌으로써 JOIN을 실행시킬 수 있죠.

 

@JoinColumn을 사용해주면 해당 컬럼에 우리는 특정 객체를 넣어줄 수 가 있게 되고,

 

@OneToMany(mappedBy = "해당컬럼") 을 사용하면 특정 객체가 그곳으로 들어갔을 때 들어간 객체에도 자동으로 맵핑을 해줄 수 있게 됩니다.

 

게시판을 하나 만들고, 거기에 일일이 댓글들을 넣어주는 것과, 댓글을 만들고, 댓글에 일일이 특정 게시글을 넣어주는 것

 

전자는 @JoinColumn을 게시판 엔터티의 댓글에 걸어주는 경우고 후자는 @JoinColumn을 댓글 엔터티의 게시판에 걸어주는 예시겠죠.

 

무엇이 더 나은 방법일까요? 연관관계의 주인이란, 연관을 더 하고싶어하는 쪽입니다.

 

게시판은 댓글이 없어도 되지만, 댓글은 게시판이 존재해야 달릴 수 있습니다. 연관관계가 더 절실한 쪽은 바로 댓글쪽입니다.

 

연관관계의 주인에게 연관하고싶은 객체를 만들어 JoinColumn 어노테이션을 붙이는 것이 더 나은 방법입니다.

 

회원과 주문정보에 대해서도 한번 다시 예를 들어보겠습니다.

 

회원은 주문정보가 없어도 되지만, 주문정보는 회원이 존재하지 않으면 안됩니다. 주문정보가 회원과 더 연관관계에 있어 절실합니다. 즉 주문정보에 회원 객체를 만들어 JoinColumn을 만들어주는 것이 좋습니다.

 


 1. N:1 맵핑

 

N:1 맵핑은 위에서 설명한것처럼 연관관계의 주인에게 JoinColumn을 설정해줘서 설정해줄 수 있습니다.

 

주문 정보 Entity를 하나 만들어보도록 하겠습니다.

 

package com.example.demo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name="orders")
public class Order {
	
	@Id @GeneratedValue
	@Column(name="ORDER_ID")
	private Long id;
	
	@ManyToOne
	@JoinColumn(name="MEMBER_ID")
	private Member member;

	public Order() {
		super();
	}


	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;
	}

	
	
}

 

ManyToOne 어노테이션과 JoinColumn을 만들어줘서 해당 객체를 받을 수 있게 되었습니다.

 

게시판에 엔티티를 불러서 해당 게시판의 댓글을 불러오고 싶을 수도 있으니 mappedBy를 이용해서 객체를 하나 만들고 자동으로 연동이 되게 해줍시다.

package com.example.demo;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;


@Entity
@Table(name = "member")
public class Member {

	@Id @GeneratedValue
	@Column(name = "MEMBER_ID")
	private Long id;
	
	@Column(name = "USERNAME")
	private String username;
	
	@OneToOne(mappedBy = "member")
	@JoinColumn(name="LOCKER_ID")
	private Locker locker;


	@OneToMany(mappedBy = "member")
	private List<Order> order = new ArrayList<>();
	
	public Member() {
		super();
	}
	
	public List<Order> getOrder() {
		return order;
	}
	
	public void setOrder(List<Order> order) {
		this.order = order;
	}

	public Member(Long id, String username, Locker locker) {
		super();
		this.id = id;
		this.username = username;
		this.locker = locker;
	}

	public Long getId() {
		return id;
	}

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

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public Locker getLocker() {
		return locker;
	}

	public void setLocker(Locker locker) {
		this.locker = locker;
	}
	
	
	
}

@OneToMany를 어노테이션을 이용붙인 Orders 리스트를 하나 만들어줍니다.

 

여기에 들어가는 Order 엔터티의 member객체와 매핑지어줄 것이기 때문에 mappedBy에 member를 넣어줍니다.

 

이렇게 해주면

 

package com.example.demo;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceUnit;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class JpaStudyApplication {

	@PersistenceUnit
	private EntityManagerFactory emf;
	public static void main(String[] args) {
		ApplicationContext context =  SpringApplication.run(JpaStudyApplication.class, args);
		
		EntityManagerFactory emf = context.getBean(EntityManagerFactory.class);
		
		EntityManager em = emf.createEntityManager();
		
		EntityTransaction tx = em.getTransaction();
		
		tx.begin();
		Member member = new Member();
		Order order = new Order();
		member.setUsername("duljji2");
		Locker locker = new Locker();
		locker.setName("dulljji's locker");
		locker.setMember(member);
		
		order.setMember(member);
		
		em.persist(locker);
		em.persist(order);
		em.persist(member);
		
		
		
		
		
		tx.commit();
		
		
		
	
		
		
		
	}

}

그런 뒤 연관관계의 주인인 order에게 member를 넣어주기만 하면?

 

 

order_id가 가지고 있는 member_id 뿐 아니라, order_id에도 값이 자동으로 들어가는 것을 확인할 수 있습니다.

 

2. 1:1맵핑

 

1:1맵핑은 N:1맵핑과 비슷하게 해주면 됩니다. 다만 어노테이션이 OneToOne인것만 다르죠.

 

하지만 어노테이션에 mappedBy를 사용할 때 주의해야 할 점이 있습니다.

 

A테이블을 만들 때, B를 자동으로 생성하고 또, B테이블을 만들 때, A를 자동으로 생성하고 싶다고

 

둘 다에게 mappedBy를 걸어주면 어떻게 될까요? 아마 무한루프에 빠지게 될 것 같습니다.

 

해보니 애초에 에러가 나와서 작동도 안하지만요.

 

그래서 OneToOne 맵핑이라 하더라도 연관관계의 주인을 생각해서 한 곳에 JoinColumn을 넣어주는 것이 바람직합니다.

 

3. M:N맵핑

 

M:N맵핑은 가운데에 중계 테이블을 만들고 각각에 N:1 맵핑을 걸어주면 됩니다.

 

A B C 테이블이 있다고 가정하면, A 와 B를 1:M, C와 B도 1:N 맵핑을 해줄 경우 A 와 C가 M:N맵핑이 되는 효과를 볼 수 있습니다.