Spring 숙련 16 (1 대 1 관계) (N 대 1 관계)
1 대 1 관계
@OneToOne : 1 대 1 관계를 맺어주는 역할을 합니다.
고객 Entity와 음식 Entity가 1 대 1 관계라 가정하여 관계를 맺어보겠습니다.
@JoinColumn() 외래 키의 주인이 활용하는 애너테이션입니다.
( 컬럼명, null 여부, unique 여부 등을 지정할 수 있습니다. )
단방향 관계:
Entity에서 외래 키의 주인은 일반적으로 N(다)의 관계인 Entity 이지만 1 대 1 관계에서는 외래 키의 주인을 직접 지정해야합니다.
음식 Entity가 외래 키의 주인인 경우 :

Food
@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;
}
User
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
테스트
// 외래 키의 주인인 Food Entity user 필드에 user 객체를 추가해 줍니다.
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
food.setUser(user); // 외래 키(연관 관계) 설정
양방향 관계 :
양방향 설정 :
양방향 관계에서 외래 키의 주인을 지정해 줄 때 mappedBy 옵션을 사용합니다.
mappedBy의 속성값은 외래 키의 주인인 상대 Entity의 필드명을 의미합니다.
관계 설정 방법에 대해 정리 해보겠습니다.
( 단방향이라면 외래 키의 주인만 상대 Entity 타입의 필드를 가지면서 @JoinColumn()을 활용하여 외래 키의 속성을 설정해주면됩니다. )
! 외래 키의 주인 Entity에서 @JoinColumn() 애너테이션을 사용하지 않아도 default 옵션이 적용되기 때문에 생략이 가능합니다.
다만 1 대 N 관계에서 외래 키의 주인 Entity가 @JoinColumn() 을 생략한다면
JPA가 외래 키를 저장할 컬럼을 파악할 수가 없어서 의도하지 않은 중간 테이블이 생성됩니다.
+ Jpa 는 @mapedBy() 가 없으면 외래키 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;
}
테스트
@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);
}
08. N 대 1 관계
@ManyToOne 애너테이션은 N 대 1 관계를 맺어주는 역할을 합니다.
음식 Entity와 고객 Entity가 N 대 1 관계라 가정하여 관계를 맺어보겠습니다.
음식 Entity가 N의 관계로 외래 키의 주인 :
단방향 관계 :

Food
@Entity
@Getter
@Setter
@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;
}
User
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
테스트
@Transactional
@SpringBootTest
public class OneToOneTest {
@Autowired
userRepository UserRepository;
@Autowired
foodRepository FoodRepository;
@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);
}
}
양방향 관계 :

고객 Entity에서 Java 컬렌션을 사용하여 음식 Entity 참조.
User 에
addFoodList() 함수 추가
@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); // 외래 키(연관 관계) 설정
}
}
테스트 :
@Transactional
@SpringBootTest
public class ManyToOne{
@Autowired
userRepository userRepository;
@Autowired
foodRepository foodRepository;
@Test
@Rollback(value = false)
@DisplayName("N대1 양방향 테스트 : 외래 키 저장 실패")
void test2() {
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
Food food2 = new Food();
food2.setName("양념 치킨");
food2.setPrice(20000);
// 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
User user = new User();
user.setName("Robbie");
user.getFoodList().add(food);
user.getFoodList().add(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
// 확인해 보시면 user_id 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}
@Test
@Rollback(value = false)
@DisplayName("N대1 양방향 테스트 : 외래 키 저장 실패 -> 성공")
void test3() {
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
Food food2 = new Food();
food2.setName("양념 치킨");
food2.setPrice(20000);
// 외래 키의 주인이 아닌 User 에서 Food 를 쉽게 저장하기 위해 addFoodList() 메서드 생성하고
// 해당 메서드에 외래 키(연관 관계) 설정 food.setUser(this); 추가
User user = new User();
user.setName("Robbie");
user.addFoodList(food);
user.addFoodList(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
}
@Test
@Rollback(value = false)
@DisplayName("N대1 양방향 테스트")
void test4() {
User user = new User();
user.setName("Robbert");
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
food.setUser(user); // 외래 키(연관 관계) 설정
Food food2 = new Food();
food2.setName("아보카도 피자");
food2.setPrice(50000);
food2.setUser(user); // 외래 키(연관 관계) 설정
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
}
}
조회
@Transactional
@SpringBootTest
public class ManyToOne_Search{
@Autowired
userRepository userRepository;
@Autowired
foodRepository foodRepository;
@Test
@DisplayName("N대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("N대1 조회 : User 기준 food 정보 조회")
void test6() {
User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
// 고객 정보 조회
System.out.println("user.getName() = " + user.getName());
// 해당 고객이 주문한 음식 정보 조회
List<Food> foodList = user.getFoodList();
for (Food food : foodList) {
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
}
}
Hibernate:
select
u1_0.id,
u1_0.name
from
users u1_0
where
u1_0.id=?
user.getName() = Robbie
Hibernate:
select
fl1_0.user_id,
fl1_0.id,
fl1_0.name,
fl1_0.price
from
food fl1_0
where
fl1_0.user_id=?