[Spring] Entity간의 연관 관계

thingzoo·2023년 7월 3일
0

Spring

목록 보기
44/54
post-thumbnail

DB table간의 연관 관계

관계형 데이터베이스는 테이블끼리 관계를 맺을 수 있다.
관계는 논리적으로 연관이 있는 두 테이블 사이의 연결을 설정한다.
이는 테이블 구조를 정제하고 중복 데이터를 최소화하는 것을 도와준다.

  • 1:1 관계
    • 하나의 테이블이 상대 테이블과 반드시 단 하나의 관계를 가지는 것
  • 1:N 관계
    • 한 쪽 테이블의 레코드가 관계를 맺은 테이블의 여러 레코드와 연결된다는 것
    • 외래키를 이용하고, 부모와 자식 관계라고도 한다.
  • N:M 관계
    • 양쪽 엔티티 모두에서 1:N 관계를 가지는 것
    • 두 테이블의 대표키를 컬럼으로 갖는 연결 테이블을 생성해서 관리한다.

Entity간의 연관 관계

  • DB 테이블에서는 테이블 사이의 연관관계를 FK(외래 키)로 맺을 수 있고 방향 상관없이 조회가 가능하다.
  • Entity에서는 상대 Entity를 참조하여 Entity 사이의 연관관계를 맺을 수 있다.
  • 하지만 상대 Entity를 참조하지 않고 있다면 상대 Entity를 조회할 수 있는 방법이 없다.
  • 따라서 Entity에서는 DB 테이블에는 없는 방향의 개념이 존재합니다.
    • 방향에는 크게 단방향과 양방향이 있다.
    • 단방향은 한 테이블에서만 다른 테이블을 참조할 수 있을 때
    • 양방향은 두 테이블이 서로를 참조할 수 있을 때

1:1 관계

  • @OneToOne: 1 대 1 관계를 맺어주는 역할

단방향

👀 외래키(FK)의 주인(Owner)

  • FK 주인만이 FK등록, 수정, 삭제할 수 있으며, 주인이 아닌 쪽은 오직 FK를 읽기만 가능!
  • Entity에서 FK의 주인은 일반적으로 N(다)의 관계인 Entity 이지만 1:1 관계에서는 FK의 주인을 직접 지정해야함
  • @JoinColumn(): FK 속성 설정
    • 외래키(FK)의 주인(Owner) 엔티티에서 사용
    • FK의 컬럼명, null 여부, unique 여부 등을 지정

// 음식(FK 주인)
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne // 1:1 지정
    @JoinColumn(name = "user_id") // FK 지정
    private User user;
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

양방향

  • mappedBy 옵션: 양방향 관계에서 FK의 주인을 지정
    • mappedBy의 속성값: FK의 주인인 상대 Entity에서 FK를 가지는 필드명을 의미

🤔 @JoinColumn() 생략가능하지만 꼭 설정해주자.

  • 1:N 관계에서 FK의 주인 Entity가 @JoinColumn() 애너테이션을 생략한다면 JPA가 FK를 저장할 컬럼을 파악할 수가 없어서 의도하지 않은 중간 테이블이 생성된다.
  • 양방향 관계에서 mappedBy 옵션을 생략할 경우 JPA가 FK의 주인 Entity를 파악할 수가 없어 의도하지 않은 중간 테이블이 생성되기 때문에 반드시 설정해주는게 좋다.

// 음식(FK 주인)
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne // 1:1 지정
    @JoinColumn(name = "user_id") // FK 지정
    private User user; // 요 user가
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToOne(mappedBy = "user") // 이 user다
    private Food food;
}

N:1 관계

  • @ManyToOne: N 대 1 관계를 맺어주는 역할

단방향

// 음식(FK 주인)
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne // N:1 지정
    @JoinColumn(name = "user_id") // FK 지정
    private User user;
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

양방향

// 음식(FK 주인)
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne // N:1 지정
    @JoinColumn(name = "user_id") // FK 지정
    private User user; // 요 user가
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "user") // 이 user다
    private List<Food> foodList = new ArrayList<>();
}

1:N 관계

  • @OneToMany:1 대 N 관계를 맺어주는 역할

단방향

  • FK를 관리하는 주인은 음식 Entity이지만 실제 FK는 고객 Entity가 가지고 있다.
    • 1 : N에서 N 관계의 테이블이 FK를 가질 수 있기 때문에 FK는 N 관계인 users 테이블에 FK 컬럼을 만들어 추가하지만 FK의 주인인 음식 Entity를 통해 관리한다.
// 음식(FK 주인)
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToMany // 1:N 지정
    @JoinColumn(name = "food_id") // FK 지정
    private List<User> userList = new ArrayList<>();
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
  • FK를 음식 Entity가 직접 가질 수 있다면 INSERT 발생 시 한번에 처리할 수 있지만, 실제 DB에서 FK를 고객 테이블이 가지고 있기 때문에 추가적인 UPDATE가 발생된다는 단점이 존재!!!

양방향

  • 1 대 N 관계에서는 일반적으로 양방향 관계가 존재하지 않는다.
  • 1 대 N 관계에서 양방향 관계를 맺으려면 음식 Entity를 FK의 주인으로 정해주기 위해 고객 Entity에서 mappedBy 옵션을 사용해야 하지만 @ManyToOne 애너테이션은 mappedBy 속성을 제공하지 않는다.
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

		@ManyToOne
		@JoinColumn(name = "food_id", insertable = false, updatable = false)
		private Food food;
}
    • N 관계의 Entity인 고객 Entity에서 @JoinColum의 insertable 과 updatable 옵션을 false로 설정하여 양쪽으로 JOIN 설정을 하면 양방향처럼 설정할 수는 있다.

N:M 관계

  • @ManyToMany: N 대 M 관계를 맺어주는 역할

단방향

// 음식(FK 주인)
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToMany // N:M 지정
    @JoinTable(name = "orders", // 중간 테이블 생성
    joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
    inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
    private List<User> userList = new ArrayList<>();
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

🚨 생성되는 중간 테이블에는 PK가 없어서 컨트롤하기 어렵기 때문에 추후에 중간 테이블의 변경이 발생할 경우 문제가 발생할 가능성이 있다.

양방향

// 음식(FK 주인)
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToMany // N:M 지정
    @JoinTable(name = "orders", // 중간 테이블 생성
    joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
    inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
    private List<User> userList = new ArrayList<>(); // 요 userList가
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @ManyToMany(mappedBy = "userList") // 이 userList다
    private List<Food> foodList = new ArrayList<>();
}

중간 테이블 직접 생성 ✨

  • 중간 테이블 orders를 직접 생성하여 관리하면 변경 발생 시 컨트롤하기 쉽기 때문에 확장성에 좋다.
// 음식
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToMany(mappedBy = "food")
    private List<Order> orderList = new ArrayList<>();
}

// 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Order> orderList = new ArrayList<>();
}

// 주문
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "food_id")
    private Food food;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
profile
공부한 내용은 바로바로 기록하자!

0개의 댓글