Spring

Spring 숙련 17 (1 대 N 관계) (N 대 M 관계)

sehunbang 2024. 1. 30. 18:34

1 대 N 관계

@OneToMany 

 

단방양

 

 

 

Foreign Key를 관리하는 주인은 음식 이지만

실제 Foreign Key 는 고객 Entity가 가지고 있습니다

 

Foreign Key 의 주인이 Foreign Key 를 컨트롤 할수 있습니다.

그렇기 때문에 List (Only Entity) 필드를 통해서 키를 성정 할수 있습니다.

 

한계점 :

실제 DB에서 외래 키를 고객 테이블이 가지고 있기 때문에

추가적인 UPDATE가 발생된다는 단점이 존재합니다.

 

음식 

@Entity
@Getter
@Setter
@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
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

양방양 :

유저에 food 로 연결 고리 추가

  • 1 대 N 관계에서는 일반적으로 양방향 관계가 존재하지 않습니다.
  • 1 대 N 관계에서 양방향 관계를 맺으려면 음식 Entity를 외래 키의 주인으로 정해주기 위해 고객 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;
}

 

테스트

@Test
@Rollback(value = false)
@DisplayName("1대N 단방향 테스트")
void test1() {
    User user = new User();
    user.setName("Robbie");

    User user2 = new User();
    user2.setName("Robbert");

    Food food = new Food();
    food.setName("후라이드 치킨");
    food.setPrice(15000);
    food.getUserList().add(user); // 외래 키(연관 관계) 설정
    food.getUserList().add(user2); // 외래 키(연관 관계) 설정

    userRepository.save(user);
    userRepository.save(user2);
    foodRepository.save(food);
    // 추가적인 UPDATE 쿼리 발생을 확인할 수 있습니다.
}

 

 

조회 :

@Test
@DisplayName("1대N 조회 테스트")
void test2() {
    Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
    System.out.println("food.getName() = " + food.getName());

    // 해당 음식을 주문한 고객 정보 조회
    List<User> userList = food.getUserList();
    for (User user : userList) {
        System.out.println("user.getName() = " + user.getName());
    }
}

 

 

 

 

 

 

 

N 대 M 관계

@ManyToMany

@JoinTable()

단방향 :

 

 

N : M 관계를 풀어내기 위해 중간 테이블(orders)을 생성하여 사용합니다.

 

음식 Entity가 외래 키의 주인

@Entity
@Getter
@Setter
@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
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

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

 

양방향 

반대 방향인 고객 Entity에 @ManyToMany 로

음식  연결하고

mappedBy 옵션을 설정하여

외래 키의 주인을 설정하면 양방향 관계 맺음이 가능합니다.

 

 

@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<>();
}
@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<>();
}

 

 

////////

중간 테이블 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<>();
}

 

 

Order 엔티티

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

    @CreatedDate
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime orderDate;
}

 

 

테스트:

@Test
@Rollback(value = false)
@DisplayName("중간 테이블 Order Entity 테스트")
void test1() {


    User user = new User();
    user.setName("Robbie");

    Food food = new Food();
    food.setName("후라이드 치킨");
    food.setPrice(15000);

    // 주문 저장
    Order order = new Order();
    order.setUser(user); // 외래 키(연관 관계) 설정
    order.setFood(food); // 외래 키(연관 관계) 설정

    userRepository.save(user);
    foodRepository.save(food);
    orderRepository.save(order);
}

@Test
@DisplayName("중간 테이블 Order Entity 조회")
void test2() {
    // 1번 주문 조회
    Order order = orderRepository.findById(1L).orElseThrow(NullPointerException::new);

    // order 객체를 사용하여 고객 정보 조회
    User user = order.getUser();
    System.out.println("user.getName() = " + user.getName());

    // order 객체를 사용하여 음식 정보 조회
    Food food = order.getFood();
    System.out.println("food.getName() = " + food.getName());
    System.out.println("food.getPrice() = " + food.getPrice());
}

 

결과 :