단방향 관계 설정 -- 음식 Entity에서만 User 객체에 대한 정보를 조회할 수 있다.
@ManyToOne은 N대1(다대일) 관계를 맺어주는 애너테이션이다.
음식 Entity와 고객 Entity가 N대1 관계라 가정할 때,
(다른 종류의 음식) 여러 개의 음식을 한 사람이 주문할 수 있다?
→ 고객 Entity의 입장에서 한 사람이 여러 개의 음식을 주문할 수 있다.
인스턴스를 생각한다면 종류가 같더라도 생성 시점에 따라 서로 다른 객체로 식별,
서로 다른 객체로 식별하는 같은 종류의 음식 Entity 인스턴스 여러 개가 한 사람에게 주문될 수 있다.
음식 Entity를 외래키의 주인으로 설정하려면
→ 음식 Entity가 고객 Entity의 주키를 외래키로 갖는 상황
→ 음식 Entity를 통해 해당 음식을 어떤 고객이 주문했는지 조회/삽입/수정이 가능한 상황
@JoinColumn 애너테이션을 통해 참조할 상대 Entity 필드를 설정함,
상대 Entity의 주키가 @JoinColumn 애너테이션 속성인 name에 설정한 값(user_id)에 삽입됨
* @JoinColumn 애너테이션을 갖는 Entity가 외래키의 주인이다.
@Entity // JPA가 관리하는 Entity 클래스임을 설정
@Table(name="food") // @Table 애너테이션, 속성 name 설정으로 클래스명과 다른 테이블명을 설정할 수 있음
@Setter
@Getter
public class Food {
@Id // primary key 설정
@GeneratedValue(strategy=GenerationType.IDENTITY) // auto_increment 설정
private Long id; // 음식 Entity 객체를 식별하는 id, Long --> bigint
private String name; // 음식명
private double price; // 음식 가격
@ManyToOne // 다대일 관계 설정
// User타입 user를 참조하는 값을 user_id에 삽입될 것을 설정
// @JoinColumn 애너테이션을 갖는 Entity 클래스가 외래키의 주인이 된다.
// 외래키의 주인이 된다는 것은 해당(참조 필드 타입 --> User) 객체에 대한 정보를
// 이 Entity 객체(Food)에서 조회할 수 있다는 것을 의미한다.
@JoinColumn(name="user_id")
private User user; // User타입 user
}
양방향 관계 설정
음식 Entity에서 고객 Entity의 정보를 조회할 수 있고,
고객 Entity에서 음식 Entity의 정보를 조회할 수 있다.
양방향 참조를 위해 고객 Entity에서 Java 컬렉션을 사용하여 음식 Entity를 참조
(음식 Entity가 N의 관계로 외래키의 주인, 보통 N:1에서 N에 해당하는 Entity가 외래키의 주인이 된다)
@Entity
@Table(name="users")
@Setter
@Getter
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
// 음식 Entity가 외래키의 주인일 때 -----------
// @OneToMany: 고객 한 명이 여러 개의 음식을 주문할 수 있다.
// mappedBy는 (외래키의 주인이 아닌 Entity에서) 외래키의 주인을 설정할 때 사용하는 옵션이다.
// --> mappedBy에 설정된 값은("user") 상대 Entity에 설정된 자기 자신 Entity의 설정명이다.
// Food Entity가 User타입을 외래키로 갖으며(Food Entity가 외래키의 주인)
// Food Entity에 설정된 User 타입의 필드명을 mappedBy의 값에 설정한다.
@OneToMany(mappedBy="user")
private List<Food> foodList = new ArrayList<>();
/*
외래키의 주인이 아닌 User에서 Food를 쉽게 저장하기 위해
addFoodList() 메서드를 생성하고,
해당 메서드에 외래 키(연관 관계) 설정 food.setUser(this); 추가
*/
public void addFoodList(Food food) {
this.foodList.add(food);
food.setUser(this); // 외래키(연관관계) 설정
}
}
@ManyToOn 코드 실습
package com.sparta.jpaadvance.relation;
import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import java.util.List;
import java.util.logging.Logger;
@SpringBootTest
@Transactional
public class ManyToOneTest {
@Autowired
private FoodRepository foodRepository;
@Autowired
private UserRepository userRepository;
@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를 쉽게 저장하기 위해
// addFoodList() 메서드를 생성하고, 해당 메서드에 외래키(연관 관계) 설정 food.setUser(this); 추가
User user = new User();
user.setName("박성문");
user.addFoodList(food);
user.addFoodList(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
}
@Test
@Rollback(value = false)
@DisplayName("N대1 양방향 테스트")
void test3() {
User user = new User();
user.setName("박성주");
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);
}
@Test
@DisplayName("N대1 조회: food Entity를 기준으로 user 정보를 조회하기")
void test4() {
// Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
Food food = foodRepository.findById(1L).orElse(null);
// 음식이름 조회
System.out.println("food.getName(): "+food.getName());
// 음식을 주문한 고객 정보 조회
System.out.println("food.getUser().getName(): "+food.getUser().getName());
}
@Test
@DisplayName("N대1 조회: User 기준 Food 정보 조회")
void test() {
Logger log = Logger.getLogger("User 기준 Food 정보 조회");
User user = userRepository.findById(1L).orElse(null);
List<Food> foodList = user.getFoodList();
log.info("for(Food food: foodList)");
for (Food food: foodList) {
log.info("food.getName(): "+food.getName());
log.info("food.getPrice(): "+food.getPrice());
}
}
}
1대N 관계에서 단방향 관계 설정
1대N 관계(1:N) 를 설정하는 @OneToMany 애너테이션
음식 Entity와 고객 Entity가 1:N의 관계라 가정할 때,
(한 종류의 음식은) 여러 명의 고객에게 주문될 수 있다.
1:N 관계에서 N관계의 테이블이 외래키를 가질 수 있기 때문에 외래키는
N관계인 users 테이블에 외래키 컬럼을 만들어 추가하지만
외래키의 주인인 음식 Entity를 통해 관리한다.
@Entity
@Table(name="food")
public class Food{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// 1대N 관계를 설정하는 애너테이션
// 한 종류의 음식을 여러 고객이 주문할 수 있다.
@OneToMany
// users 테이블에 food_id 컬럼을 설정하고, food Entity가 관리한다.
@JoinColumn(name="food_id")
private List<User> userList = new ArrayList<>();
}
1:N 관계에서 user를 조회하는 테스트 코드 실습
package com.sparta.jpaadvance.relation;
import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import java.util.List;
import java.util.logging.Logger;
@SpringBootTest
public class OneToManyTest {
@Autowired
private UserRepository userRepository;
@Autowired
private FoodRepository foodRepository;
@Test
@DisplayName("1대N 조회 테스트")
void test() {
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());
}
}
}
다대다(M:N) 관계 설정
@ManyToMany 애너테이션을 사용하여 다대다(N:M) 관계 설정
@ManyToMany 애너테이션 사용시 @JoinTable 애너테이션 사용으로 중간 테이블을 만들 수 있다.
(음식 Entity가 외래키의 주인이라는 설정하에)
// 외래키의 주인인 Food Entity
@Entity
@Getter
@Setter
@Table(name="food")
public class Food {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@JoinTable(
name="orders" // name 속성은 현재 Entity 객체와 조인할 테이블 이름 설정
// 현재 Entity 객체의(Food) 주키(pk)와 조인할 orders 테이블의 컬럼명 설정?
joinColumns=@JoinColumns(name="food_id"),
// 현재 Food Entity 객체를 기준으로
// 반대 위치에 있는 Entity 즉 User Entity의 주키를
// 중간 테이블의 외래키로 설정?하는데 해당 값을 갖는 컬럼명 설정?
inverseJoinColumns=@JoinColums(name="user_id")
)
@ManyToMany // Food Entity와 User Entity는 다대다 관계임을 설정
List<User> userList = new ArrayList<>();
public void addUserList(User user) {
this.userList.add(user); // 외래키 (연관관계) 설정
user.getFoodList().add(this);
}
}
// User Entity
// @ManyToMany 애너테이션과 mappedBy 옵션 설정으로 양방향 관계 설정
// --> User Entity에서 Food Entity 정보를 조회할 수 있음
@Entity
@Table(name="users")
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany // M:N 관계설정
// 외래키의 주인인 Entity에서 → Food Entity
// 해당 Entity 타입을(User Entity) 갖는 참조변수명을 mappedBy의 값으로 작성
(mappedBy="userList")
List<Food> foodList = new ArrayList<>();
// addFoodList() 메서드 설정
public void addFoodList(Food food) {
this.foodList.add(food);
food.getUserList().add(this); // 외래키 (연관관계) 설정
}
}
Food Entity 객체로 주문 테이블을 통해 해당 food를 주문한 고객 정보를 조회할 수 있음.
JPA에 의해 중간 테이블인 orders 테이블이 생성되고, JPA에 의해 생성됐기에? pk가 없음.
Food Entity의 주키를 외래키로 갖는 food_id 컬럼과 User Entity의 주키를 외래키로 갖는 user_id 컬럼이 존재함.
@ManyToMany 애너테이션으로 다대다(M:N) 관계임을 설정하고,
→ 하나의 음식은 여러명의 고객에게 주문될 수 있고, 한 명의 고객은 여러 개의 음식을 주문할 수 있다.
상대 Entity의 타입을 필드로 가져서 해당 음식을 주문한 고객에 대한 정보를 알 수 있다.
└ List<User> userList = new ArrayList<>();
지연로딩
FetchType.LAZY는 지연로딩이다. 지연로딩은 필요한 시점에 정보를 가져온다.
FetchType.EAGER는 즉시로딩이다. 즉시로딩은 즉시 정보를 가져온다.
@OneToMany는 기본 값은 FetchType.LAZY로 필요할 때 정보 조회가 이루어진다.
└ 한 명의 고객은 여러 개의 음식을 주문할 수 있다.
@ManyToOne 설정은 기본 값이 FetchType.EAGER로 즉시 정보를 가져온다.
영속성전이
영속상태의 Entity에서 수행되는 작업들이 연관된 Entity까지 전파되는 상황을 의미한다.
// User Entity
/* cascade=CascadeType.PERSIST: 영속성 전이 설정
Food타입 객체가 리스트타입으로 쌓여있는데,
이 공간에 Food타입 객체가 추가(또는 삭제 등) 되면
기존에는 FoodRepository타입 참조변수의 save() 메서드를 통해 테이블에 변경이 발생했는데,
cascade=CascadeType.PERSIST 를 설정했으므로 UserRepository 타입 참조변수를 통해
save() 메서드를 호출하는 것만으로 FoodRepository 타입 참조변수의 save() 메서드를 호출한 결과와 같다.
*/
@OneToMany(mappedBy="user", cascade=CascadeType.PERSIST)
private List<Food> foodList = new ArrayList<>();
@OneToMany 애너테이션의 옵션 설정중 영속성 전이와 관련된 cascade
@OneToMany(mappedBy="user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
mysql command line 에서 사용하는 명령어
# 데이터베이스 목록 요청
show databases;
# 데이터베이스 선택
use 데이터베이스명;
# 선택한 데이터베이스 확인
select database();
# 테이블 목록 요청
show tables;
# 테이블 삭제하려는데 제약조건 걸려있을 때, 해당 제약조건 제거하기
alter table 테이블명 drop constraint 제약조건명;
# 테이블 삭제하기
drop table 테이블명;
'[부트캠프] kdt 심화 과정' 카테고리의 다른 글
2025년 3월 5일(수) (1) | 2025.03.05 |
---|---|
2025년 3월 4일(화) (0) | 2025.03.04 |
[스파르타코딩] 스프링 입문과 숙련 주차(1) (1) | 2025.02.07 |
[스파르타코딩] 스프링 입문 주차(2) (1) | 2025.02.04 |
[스파르타코딩] 스프링 입문 주차(1) (1) | 2025.01.23 |