[SPRING] Entity 연관 관계

야부엉·2023년 11월 16일
0

1. 1 대 1 관계

1. @OneToOne

  • @OneToOne 에너테이션은 1 대 1 관계를 맺어주는 역할을 한다
  • 고객 Entity와 음식 entity가 1 : 1 관계를 맺었다고 가정해 보자

2. 단방향 관계

(1) 외래키 주인 정하기

  • 단방향 관계에서 가장 중요한 것은 외래키 주인을 정하는 것이다.
  • 외래키 주인만이 외래 키를 등록, 수정, 삭제 할 수 있으며, 주인이 아닌 쪽은 오직 외래키를 읽을 수 밖에 없기 때문이다.
  • 보통은 Entity에서 외래키의 주인을 정할 때, N(다)의 관계인 Entity로 정하고, 1대 1관계는 직접 정한다.
  • 외래키의 주인은 곧 그 테이블의 존재하는 foreign Key의 위치라고 생각하면 된다.
    (2) @JoinColumn()
  • @JoinColumn()은 외래키의 주인을 활용하는 어노테이션이다.
  • 칼럼명, Null 여부, unique 여부 등을 지정 가능하다.
  • 위의 그림과 같이 단방향으로 설정을 하기 위해서는 아래의 코드와 같이 작성을 해야한다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
  • 1 : 1 관계이기 때문에 @OneToOne을 사용했고, @JoinColumn을 사용함으로써 외래키의 주인을 설정했다.
  • name을 "user-id"를 사용함으로써, user_id 값을 Foreign Key로 설정을 했다고 생각하면 된다(Column도 user_id로 저장).
  • 단방향이기 때문에 음식테이블에서만 고객 테이블 조회가 가능하다.

2. 양방향 관계

(1) 양방향 관계 설정

  • JPA에서는 양방향 관계일 때, 외래키의 주인은 누구인지 표시해줘야 한다.
  • @OneToOne(mappedBy = "user")를 사용해서 외래키의 주인이 아닌 쪽에서, 외래키 주인을 알려줘야한다.
  • mappedBy의 속성값은 외래 키의 주인인 상대 Entity의 필드명을 의미한다. -> 위의 고객 테이블과 음식 테이블에서를 생각해보면, food entity안에 있는 필드명 즉, user를 의미한다.

(2) 선언 방법

  • 음식테이블을 외래키의 주인일 때의 구현 방법입니다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToOne(mappedBy = "user")
    private Food food;

    public void addFood(Food food) {
        this.food = food;
        food.setUser(this);
    }
}
  • 외래 키의 주인만이 외래키를 조정 할 수 있기 때문에 외래키가 아닌 곳에서 조정을 할려면 외래키를 파라미터로 참조함고, 자기 자신 객체를 setUser하면서 넘겨준다.

    주의할점!!!

  1. 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 사용하지 않아도 default 옵션이 적용되기 때문에 생략이 가능하다. 하지만, 1 : N 관계에서 생략을 하게 된다면, JPA가 외래 키를 저장할 칼럼을 파악을 하지 못해서 의도하지 않은 중간 테이블이 생성 된다.
  2. 양 방향 관계에서 외래키의 주인을 표시하는 mappedBy를 생략하면, JPA는 외래키의 주인 Entity를 파악못해서 위와 같이 중간 테이블을 생성한다.

2. N 대 1 관계

1. @ManyToOne

  • @ManyToOne 애너테이션은 N : 1 관계를 맺어주는 역할을 한다.
  • 고객 Entity와 음식 Entity가 N : 1 관계를 맺었다고 가정해보자.

2. 단방향 관계

  • 음식 entity가 N인 경우 구현 방법은 아래의 코드와 같다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

3. 양방향 관계

  • 음식 Entity가 N인 경우라고 가정을 해보자
  • 음식 Entity를 구현하는 코드는 위와 같지만 고객 Entity에서 차이가 있다.
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
        this.foodList.add(food);
        food.setUser(this); // 외래 키(연관 관계) 설정
    }
  • 코드를 보면, @고객 entity에 foodList가 있는데, 이것은 객체 상태의 데이터를 참조하기 위함이고 실제 데이터 베이스 테이블에는 저장이 되지 않는다.
  • 여러 개를 저장하기 위해서 List를 사용한다.
  • 1 대 1관계와 같이 외래키의 주인 아닌 곳에서 조정을 하기위해 파라미터로 받고, 자기 자신의 객체를 넘겨준다.

3. 1 대 N 관계

1. @OneToMany

  • @OneToMany 애너테이션은 1 대 N 관계를 맺어주는 역할을 한다.
  • 음식 Entity와 고객 Entity가 1 대 N 관계라고 가정해 보자

2. 단방향 관계

  • 외래키의 주인은 N인 관계가 가진다고 알고 있다. 하지만 위의 그림을 보면 외래키를 고객이 가지고 있다.
  • 고객이 N인 상태이기 때문에 food_id라는 column은 고객 테이블에서 생기지만, 단방향이기 때문에 고객 Entity에는 food entity 필드는 없다.
  • 음식에 여러 고객이 있는 관계기 때문에 List를 이용한다. 즉, 실제 외래키의 주인은 음식이지만, 데이터베이스에 저장 되있는 곳은 고객에 있다. -> 실제 외래키를 컨트롤하는 것은 List 필드를 통해 한다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToMany
    @JoinColumn(name = "food_id") // users 테이블에 food_id 컬럼
    private List<User> userList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
  • @JoinColumn(name = "food_id") 외래 키의 주인은 food지만 실제 테이블에 저장되는 것은 상대 entity에 저장되기 때문에 name을 food_id로 설정해야한다. (관리는 food , 실제 주인은 user)

한계점 !!!
외래 키를 음식 Entity가 직접 가질 수 있다면 INSERT 발생 시 한번에 처리할 수 있지만 실제 DB에서 외래 키를 고객 테이블이 가지고 있기 때문에 추가적인 UPDATE가 발생된다는 단점이 존재한다.

3. 양방향 관계

  • 일반적으로 존재 하지 않는다.
  • 1 : N 관계를 맺을려면, 음식 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;
}
  • 위의 코드처럼 억지로 하면 참조는 가능하다. 단 비추천!!

4. M 대 N 관계

1. @ManyToMany

  • @ManyToMany 애너테이션은 N 대 M 관계를 맺어주는 역할을한다.
  • 음식 Entity와 고객 Entity가 N 대 M 관계라고 가정해보자.

2.단방향 관계

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

    @ManyToMany
    @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;
}
  • Food entity 코드를 보면 @JoinTable 옵션을 통해 중간 테이블(연결하는 테이블)을 생성한다.
  • 생성되는 중간 테이블은 컨트롤하기 어렵기 때문에 테이블의 변경에서 문제가 발생할 수 있다. -> 자동 생성되기 때문에 PK가 없다.

3. 양방향 관계

  • 음식 Entity 코드는 단방향과 같고, 고객 Entity에 차이가 난다. -> 외래키 주인 설정
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToMany(mappedBy = "userList")
    private List<Food> foodList = new ArrayList<>();
}

4. 중간 테이블 직접 생성

  • 중간 테이블 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개의 댓글