@OneToOne
- @OneToOne 애너테이션은 1 대 1 관계를 맺어주는 역할을 합니다.
- 고객 Entity와 음식 Entity가 1 대 1 관계라 가정하여 관계를 맺어보겠습니다.
단방향
외래 키의 주인 정하기
- Entity에서 외래 키의 주인은 일반적으로 N(다)의 관계인 Entity 이지만 1 대 1 관계에서는 외래 키의 주인을 직접 지정해야합니다.
- 외래 키 주인만이 외래 키를 등록, 수정, 삭제할 수 있으며, 주인이 아닌 쪽은 오직 외래 키를 읽기만 가능합니다.
- @JoinColumn()은 외래 키의 주인이 활용하는 애너테이션입니다.
- 컬럼명, null 여부, unique 여부 등을 지정할 수 있습니다.
음식 Entity가 외래 키의 주인인 경우
@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;
}
고객 Entity가 외래 키의 주인인 경우
@Entity
@Table(name = "food")
public class 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;
}
더보기
- 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 사용하지 않아도 default 옵션이 적용되기 때문에 생략이 가능합니다.
- 다만 1 대 N 관계에서 외래 키의 주인 Entity가 @JoinColumn() 애너테이션을 생략한다면 JPA가 외래 키를 저장할 컬럼을 파악할 수가 없어서 의도하지 않은 중간 테이블이 생성됩니다.
- 따라서 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 활용하시는게 좋습니다.
- 양방향 관계에서 mappedBy 옵션을 생략할 경우 JPA가 외래 키의 주인 Entity를 파악할 수가 없어 의도하지 않은 중간 테이블이 생성되기 때문에 반드시 설정해주시는게 좋습니다.
양방향
음식 Entity가 외래 키의 주인인 경우!
@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;
@OneToOne(mappedBy = "user")
private Food food;
}
고객 Entity가 외래 키의 주인인 경우!
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.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;
}
양방향 관계에서 외래 키의 주인을 지정해 줄 때 mappedBy 옵션을 사용합니다.
- mappedBy의 속성값은 `외래 키의 주인인 상대 Entity의 필드명`을 의미합니다.
>>
public class Food {
//...
private final User user; // 여기 **user**를 의미합니다.
}
관계 설정 방법에 대해 정리 해보겠습니다.
- 단방향이라면 외래 키의 주인만 상대 Entity 타입의 필드를 가지면서 @JoinColumn()을 활용하여 외래 키의 속성을 설정해주면됩니다.
- 양방향이라면 외래 키의 주인은 상대 Entity 타입의 필드를 가지면서 @JoinColumn()을 활용하여 외래 키의 속성을 설정을 해줍니다.
- 그리고 상대 Entity는 외래 키의 주인 Entity 타입의 필드를 가지면서 mappedBy 옵션을 사용하여 속성 값으로 외래 키의 주인 Entity에 선언된 @JoinColumn()으로 설정되고 있는 필드명을 넣어주면 됩니다.
'외래 키 주인만이 외래 키를 등록, 수정, 삭제할 수 있으며, 주인이 아닌 쪽은 오직 외래 키를 읽기만 가능합니다.'의 의미
전제: 음식 Entity가 외래 키의 주인이고, 고객 Entity와 양방향 관계이다.
@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); // 외래 키의 주인인 food에서 user 저장 -> 가능
userRepository.save(user);
foodRepository.save(food);
}
@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); // 외래 키의 주인이 아닌 user 에서 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);
/*
@Entity
public class User {
//...
public void addFood(Food food) {
this.food = food;
food.setUser(this);
}
}
*/
}
양쪽에서 조회 가능
@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());
}
'Spring > 팀스파르타' 카테고리의 다른 글
38. Entity 연관관계 - 1 대 N (0) | 2024.12.06 |
---|---|
37. Entity 연관관계 - N 대 1 (0) | 2024.12.06 |
35. Entity 연관 관계 기초 (1) | 2024.11.28 |
34. RestTemplate의 Post 요청, exchange (0) | 2024.11.27 |
33. RestTemplate이란 무엇일까? (0) | 2024.11.25 |