[내배캠 Java 6기] Spring 주특기 숙련 2주차 (6-13)

ekkkyo_j·2024년 8월 23일

[내배캠 Java 6기] TIL

목록 보기
25/38

싱글톤은 new를 여러 번해도 객체는 하나이다. Bean은 싱글톤으로 등록해준다. 동시성 때문!

Builder 패턴

6. Entity 연관 관계

📌 다대일 관계(N:1)

음식 : 고객 = N : 1

양방향 관계

- 음식에서도 고객을 조회하고 고객도 음식을 조회할 수 있도록 하는 JPA Entity
  • Food entity
@Entity
@Table
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;
}

@ManyToOne
: @ManyToOne이 선언된 Entity가 Many.
Food가 Many, User가 1.

@JoinColumn(name="user_id")
private User user;
: User user 객체는 user_id column을 참조한다.

  • User entity
@Entity
@Table
public class User {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToMany(mapped by "user")
    private List<Food> foodList = new ArrayList<>();

}
  • 한명의 고객은 여러번 주문이 가능한 상황이다.
    - 이를 Entity에서 여러번 가능함을 표현하기 위해 Java 컬렉션을 사용하여 List<Food> foodList = new ArrayList<>() 이처럼 표현할 수 있다.
  • 그렇다면 Entity에서 이렇게까지 해서 표현을 하는 이유는?
    • DB 테이블에서는 고객 테이블 기준으로 음식의 정보를 조회하려고 할 때 JOIN을 사용하여 바로 조회가 가능하지만 고객 Entity 입장에서는 음식 Entity의 정보를 가지고 있지 않으면 음식의 정보를 조회할 방법이 없다.
    • 따라서 DB 테이블에 실제 컬럼으로 존재하지는 않지만 Entity 상태에서 다른 Entity를 참조하기 위해 이러한 방법을 사용한다.
  • 현재 음식 Entity와 고객 Entity는 서로를 참조하고 있다.
    • 이러한 관계를 양방향 관계라 부른다.

단방향 관계

- 음식 Entity에서만 고객 Entity를 참조할 수 있다.
  • Food entity
@Entity
@Table
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;
}
  • User entity
@Entity
@Table
public class User {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
  • 양방향 관계와 다르게 Food를 담은 리스트가 존재하지 않는다.
  • 따라서, 음식 Entity에서만 고객 Entity를 참조할 수 있다.
    • 이러한 관계를 단방향 관계라 부른다.
    • 고객 Entity에는 음식 Entity의 정보가 없기 때문에 음식 정보를 조회할 수 없다.

7. 일대일 관계(1:1)

@OneToOne

단방향 관계

  • FK의 주인 정하기
    • Entity에서 FK의 주인은 일반적으로 N(다)의 관계인 Entity 이지만 1 대 1 관계에서는 FK의 주인을 직접 지정해야한다.

      FK의 주인만이 FK등록, 수정, 삭제할 수 있으며, 주인이 아닌 쪽은 오직 FK를 읽기만 가능하다.

    • @JoinColumn()은 FK의 주인이 활용하는 어노테이션이다.
      • 컬럼명, null 여부, unique 여부 등을 지정할 수 있다.

음식 Entity가 FK의 주인인 경우!

  • 음식
@Entity
@Table(name="food")
public clas 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;
}

고객 Entity가 외래 키의 주인인 경우!

  • 음식
@Entity
@Table(name="food")
public clas Food {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;
  • 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToOne
    @JoinColumn(name = "food_id")
    private Food food;
}

OneToOne 단방향 테스트
: Food가 FK의 주인 entity

@Test
@Rollback(value = false) // 테스트에서는 @Transactional 에 의해 자동 rollback 됨으로 false 설정해준다.
@DisplayName("1대1 단방향 테스트")
void test1() {

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

    // 외래 키의 주인인 Food Entity user 필드에 user 객체를 추가해 줍니다.
    Food food = new Food();
    food.setName("후라이드 치킨");
    food.setPrice(15000);
    food.setUser(user); // 외래 키(연관 관계) 설정

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

양방향 관계

  • 양방향 설정
    • 양방향 관계에서 FK의 주인을 지정해 줄 때 mappedBy 옵션을 사용한다.
      • mappedBy의 속성값은 FK의 주인인 상대 Entity의 필드명을 의미한다.
    • 관계 설정 방법에 대한 정리
      • 단방향이라면 FK의 주인만 상대 Entity 타입의 필드를 가지면서 @JoinColumn()을 활용하여 FK의 속성을 설정해주면 된다.
      • 양방향이라면 FK의 주인은 상대 Entity 타입의 필드를 가지면서 @JoinColumn()을 활용하여 FK의 속성을 설정을 해준다.
        • 그리고 상대 Entity는 FK의 주인 Entity 타입의 필드를 가지면서 mappedBy 옵션을 사용하여 속성 값으로 FK의 주인 Entity에 선언된 @JoinColumn()으로 설정되고 있는 필드명을 넣어주면 된다.

음식 Entity가 외래 키의 주인인 경우!

  • 음식
@Entity
@Table(name = "food")
public class Food {
	@Id
    @GeneratedValue(strategy = GenrationType.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;
    
    @OneToOne(mappedby ="user")
    private Food food;
}


고객 Entity가 외래 키의 주인인 경우!

  • 음식
@Entity
@Table(name = "food")
public class Food {
	@Id
    @GeneratedValue(strategy = GenrationType.IDENTITY)
    private Long id;
    private String name;
    private double price;
    
    @OneToOne(mappedBy = "food")
    private User user;
    
  • 고객
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToOne
    @JoinColumn(name = "food_id")
    private Food food;

양방향 OneToOne 테스트

Test2(실패)
: 외래 키의 주인은 Food인데 주인이 아닌 User에서 Food를 저장한다.
=> Food의 외래 키인 user_id값이 저장되지 않는다.

Test3(실패 -> 성공)
: 아래 고객 엔티티를 보면 addFood 메서드에 food를 주입받고 food가 setUser(this) 즉, user를 등록 = 외래키를 설정 해준다.

  • 고객

Test4(성공)
: 외래 키의 주인인 Food가

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@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);
    }
}
@Test
@Rollback(value = false)
@DisplayName("1대1 양방향 테스트 : 외래 키 저장 실패")
void test2() {
    Food food = new Food();
    food.setName("고구마 피자");
    food.setPrice(30000);

    // 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
    User user = new User();
    user.setName("Robbie");
    user.setFood(food);

    userRepository.save(user);
    foodRepository.save(food);

    // 확인해 보시면 user_id 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}

@Test
@Rollback(value = false)
@DisplayName("1대1 양방향 테스트 : 외래 키 저장 실패 -> 성공")
void test3() {
    Food food = new Food();
    food.setName("고구마 피자");
    food.setPrice(30000);

    // 외래 키의 주인이 아닌 User 에서 Food 를 저장하기 위해 addFood() 메서드 추가
    // 외래 키(연관 관계) 설정 food.setUser(this); 추가
    User user = new User();
    user.setName("Robbie");
    user.addFood(food);

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

@Test
@Rollback(value = false)
@DisplayName("1대1 양방향 테스트")
void test4() {
    User user = new User();
    user.setName("Robbert");

    Food food = new Food();
    food.setName("고구마 피자");
    food.setPrice(30000);
    food.setUser(user); // 외래 키(연관 관계) 설정

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

OneToOne 조회 테스트

@Test
@DisplayName("1대1 조회 : Food 기준 user 정보 조회")
void test5() {
    Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
    // 음식 정보 조회
    System.out.println("food.getName() = " + food.getName());

    // 음식을 주문한 고객 정보 조회
    System.out.println("food.getUser().getName() = " + food.getUser().getName());
}

@Test
@DisplayName("1대1 조회 : User 기준 food 정보 조회")
void test6() {
    User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
    // 고객 정보 조회
    System.out.println("user.getName() = " + user.getName());

    // 해당 고객이 주문한 음식 정보 조회
    Food food = user.getFood();
    System.out.println("food.getName() = " + food.getName());
    System.out.println("food.getPrice() = " + food.getPrice());
}
profile
게으르지만 성실히 공부하기^_^

0개의 댓글