์ง์ฐ ๋ก๋ฉ(LAZY)๊ณผ ์ฆ์ ๋ก๋ฉ(EAGER)
- ์์ ํ ์ด๋ธ๊ณผ ๊ณ ๊ฐ ํ ์ด๋ธ์ด N:1 ๊ด๊ณ๋ก ์ค์ ํ๊ณ ์์
@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;
}
@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<>();
}
- ์ง์ฐ๋ก๋ฉ๊ณผ ์ฆ์๋ก๋ฉ ํ์ต์ ํ์ํ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ๋ฏธ๋ฆฌ ์ถ๊ฐํด๋๊ธฐ
@Test
@Transactional
@Rollback(value = false)
void init() {
// ์ง์ฐ ๋ก๋ฉ ์ ํ์ํ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ๋ฏธ๋ฆฌ ๋ง๋ค๊ธฐ
List<User> userList = new ArrayList<>();
User user1 = new User();
user1.setName("Robbie");
userList.add(user1);
User user2 = new User();
user2.setName("Robbert");
userList.add(user2);
userRepository.saveAll(userList); // saveAll(): ํ ๋ฒ์ ์ฌ๋ฌ๊ฐ save ๊ฐ๋ฅ
List<Food> foodList = new ArrayList<>();
Food food1 = new Food();
food1.setName("๊ณ ๊ตฌ๋ง ํผ์");
food1.setPrice(30000);
food1.setUser(user1); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food1);
Food food2 = new Food();
food2.setName("์๋ณด์นด๋ ํผ์");
food2.setPrice(50000);
food2.setUser(user1); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food2);
Food food3 = new Food();
food3.setName("ํ๋ผ์ด๋ ์นํจ");
food3.setPrice(15000);
food3.setUser(user1); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food3);
Food food4 = new Food();
food4.setName("ํ๋ผ์ด๋ ์นํจ");
food4.setPrice(15000);
food4.setUser(user2); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food4);
Food food5 = new Food();
food5.setName("๊ณ ๊ตฌ๋ง ํผ์");
food5.setPrice(30000);
food5.setUser(user2); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food5);
foodRepository.saveAll(foodList);
}

์๋ณด์นด๋ ํผ์๋ฅผ ์กฐํํ๋ ํ ์คํธ
@Test
@DisplayName("์๋ณด์นด๋ ํผ์ ์กฐํ")
void test1() {
Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}

FetchType
JPA์์๋ ์ฐ๊ด ๊ด๊ณ๊ฐ ์ค์ ๋ Entity์ ์ ๋ณด๋ฅผ ๋ฐ๋ก ๊ฐ์ ธ์ฌ์ง ํน์ ํ์ํ ๋ ๊ฐ์ ธ์ฌ์ง ์ ํ ์ ์์! ์ฆ, ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ด FetchType์
FetchType ์ข ๋ฅ 1) LAZY(์ง์ฐ ๋ก๋ฉ)
LAZY๋ ํ์ํ ์์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด
- @OneToMany๋ default๊ฐ LAZY์
- ์ค์ ๋ ํด๋น ํ๋๊ฐ Java Collection ํ์ ์ธ ๊ฒฝ์ฐ, ์ฆ @?ToMany ํํ์ธ ๊ฒฝ์ฐ ํจ์จ์ ์ผ๋ก ์ ๋ณด๋ฅผ ์กฐํํ๊ธฐ ์ํด default๊ฐ LAZY์
FetchType ์ข ๋ฅ 2) EAGER(์ฆ์ ๋ก๋ฉ)
EAGER์ ์ฆ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด
- @ManyToOne์ default๊ฐ EAGER์
- @?ToOne ํํ์ธ ๊ฒฝ์ฐ ์ฆ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์๋ ๋ฌด๋ฆฌ๊ฐ ์๊ธฐ ๋๋ฌธ์ default๊ฐ EAGER์
Robbie ๊ณ ๊ฐ ์กฐํ(LAZY)
@Test
@Transactional
@DisplayName("Robbie ๊ณ ๊ฐ ์กฐํ")
void test2() {
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
System.out.println("Robbie๊ฐ ์ฃผ๋ฌธํ ์์ ์ด๋ฆ ์กฐํ");
for (Food food : user.getFoodList()) {
System.out.println(food.getName());
}
}

์๋ณด์นด๋ ํผ์ ์กฐํ ์ ํผ์๋ง ์กฐํํ๊ณ ์ถ์ ๊ฒฝ์ฐ
- FetchType์ LAZY๋ก ๋ณ๊ฒฝ
@Entity
...
@Table(name = "food")
public class Food {
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}

- ์์์ฑ ์ปจํ ์คํธ ๊ธฐ๋ฅ: 1์ฐจ ์บ์, ์ฐ๊ธฐ ์ง์ฐ ์ ์ฅ์, ๋ณ๊ฒฝ ๊ฐ์ง
- ์ง์ฐ ๋ก๋ฉ๋ ์์์ฑ ์ปจํ ์คํธ ๊ธฐ๋ฅ ์ค ํ๋์
- ์ฆ, ์ง์ฐ ๋ก๋ฉ๋ Entity ์ ๋ณด๋ฅผ ์กฐํ(Select)ํ๋ ค๊ณ ํ ๋๋ ๋ฐ๋์ ์์์ฑ ์ปจํ ์คํธ๊ฐ ์กด์ฌํด์ผ ํจ
- ์์์ฑ ์ปจํ ์คํธ๊ฐ ์กด์ฌํด์ผ ํ๋ค = Spring Container ํ๊ฒฝ์์๋ ํธ๋์ญ์ ์ด ์ ์ฉ๋์ด ์์ด์ผ ํจ
@Test
@Transactional
@DisplayName("์๋ณด์นด๋ ํผ์ ์กฐํ")
void test1() {
Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
System.out.println("์๋ณด์นด๋ ํผ์๋ฅผ ์ฃผ๋ฌธํ ํ์ ์ ๋ณด ์กฐํ");
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}

์์์ฑ ์ ์ด(Cascade Persist)
- ์์ ํ ์ด๋ธ๊ณผ ๊ณ ๊ฐ ํ ์ด๋ธ์ด N:1 ๊ด๊ณ๋ก ์ค์ ํ๊ณ ์์
Robbie ์์ ์ฃผ๋ฌธ ํ ์คํธ
@Test
@DisplayName("Robbie ์์ ์ฃผ๋ฌธ")
void test1() {
// ๊ณ ๊ฐ Robbie ๊ฐ ํ๋ผ์ด๋ ์นํจ๊ณผ ์๋
์นํจ์ ์ฃผ๋ฌธํฉ๋๋ค.
User user = new User();
user.setName("Robbie");
// ํ๋ผ์ด๋ ์นํจ ์ฃผ๋ฌธ
Food food = new Food();
food.setName("ํ๋ผ์ด๋ ์นํจ");
food.setPrice(15000);
user.addFoodList(food);
Food food2 = new Food();
food2.setName("์๋
์นํจ");
food2.setPrice(20000);
user.addFoodList(food2);
userRepository.save(user);
foodRepository.save(food);
foodRepository.save(food2);
}
@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); // ์ธ๋ ํค ์ค์
}
}
- ํ๋ผ์ด๋์ ์นํจ ๊ฐ๊ฐ saveํ๊ณ User saveํ๋ ๊ฒ๋ณด๋ค, User Entity๋ง saveํ๋ฉด foodList์ ๋ค์ด์๋ Food 2๊ฐ๊ฐ ์๋์ผ๋ก ์์ํ๋๋ ๊ฒ์ด ๋ ํจ์จ์ ์
- JPA์์๋ ์ด๋ฌํ ๋ด์ฉ์ ์์ฃผ ๊ฐํธํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ ๋ฐฉ๋ฒ์ผ๋ก ์์์ฑ ์ ์ด์ persist ์ต์ ์ ๊ณต
Cascade์ persist(์์์ฑ ์ ์ด)
์์์ฑ ์ ์ด๋ ์์ ์ํ์ Entity์์ ์ํ๋๋ ์์ ๋ค์ด ์ฐ๊ด๋ Entity๊น์ง ์ ํ๋๋ ์ํฉ
- ์์์ฑ ์ ์ด๋ฅผ ์ฌ์ฉํด์ Entity๋ฅผ ์ ์ฅํ ๋, ํด๋น Entity์ ์ฐ๊ด ๊ด๊ณ์ ์๋ Entity๊น์ง ์๋์ผ๋ก ์ ์ฅํ ์ ์์
- ์ฐ๊ด๋ Entity์ Cascade persist option ์ค์ ํ๋ฉด๋จ
@Entity
...
@Table(name = "users")
public class User {
...
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
private List<Food> foodList = new ArrayList<>();
...
}
์์์ฑ ์ ์ด ์ ์ฅ ํ ์คํธ
@Test
@DisplayName("์์์ฑ ์ ์ด ์ ์ฅ")
void test2() {
// ๊ณ ๊ฐ Robbie ๊ฐ ํ๋ผ์ด๋ ์นํจ๊ณผ ์๋
์นํจ์ ์ฃผ๋ฌธํฉ๋๋ค.
User user = new User();
user.setName("Robbie");
// ํ๋ผ์ด๋ ์นํจ ์ฃผ๋ฌธ
Food food = new Food();
food.setName("ํ๋ผ์ด๋ ์นํจ");
food.setPrice(15000);
user.addFoodList(food);
Food food2 = new Food();
food2.setName("์๋
์นํจ");
food2.setPrice(20000);
user.addFoodList(food2);
userRepository.save(user);
}

์์์ฑ ์ ์ด ์ญ์ ํ ์คํธ
- ์์์ฑ ์ ์ด๋ฅผ ์ ์ฉํ์ง ์์ผ๋ฉด, ์ฃผ๋ฌธํ ์์ ๋ฐ์ดํฐ๋ฅผ ๋จผ์ ์ญ์ ํ ํ ํ์ ํํด๋ฅผ ํด์ผํจ
@Test
@Transactional
@Rollback(value = false)
@DisplayName("Robbie ํํด")
void test3() {
// ๊ณ ๊ฐ Robbie ๋ฅผ ์กฐํํฉ๋๋ค.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// Robbie ๊ฐ ์ฃผ๋ฌธํ ์์ ์กฐํ
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
// ์ฃผ๋ฌธํ ์์ ๋ฐ์ดํฐ ์ญ์
foodRepository.deleteAll(user.getFoodList());
// Robbie ํํด
userRepository.delete(user);
}
- JPA์์ Cascade.REMOVE ์ต์ ์ ๊ณตํจ
@Entity
...
@Table(name = "users")
public class User {
...
@OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Food> foodList = new ArrayList<>();
...
}
@Test
@Transactional
@Rollback(value = false)
@DisplayName("์์์ฑ ์ ์ด ์ญ์ ")
void test4() {
// ๊ณ ๊ฐ Robbie ๋ฅผ ์กฐํํฉ๋๋ค.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// Robbie ๊ฐ ์ฃผ๋ฌธํ ์์ ์กฐํ
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
// Robbie ํํด
userRepository.delete(user);
}

๊ณ ์ Entity(onphanRemoval)
- ๋จผ์ ํ ์ด๋ธ ์์ฑ ํ ๋ฐ์ดํฐ ์ฝ์ ์ํ
...
@SpringBootTest
public class OrphanTest {
@Autowired
UserRepository userRepository;
@Autowired
FoodRepository foodRepository;
@Test
@Transactional
@Rollback(value = false)
void init() {
List<User> userList = new ArrayList<>();
User user1 = new User();
user1.setName("Robbie");
userList.add(user1);
User user2 = new User();
user2.setName("Robbert");
userList.add(user2);
userRepository.saveAll(userList);
List<Food> foodList = new ArrayList<>();
Food food1 = new Food();
food1.setName("๊ณ ๊ตฌ๋ง ํผ์");
food1.setPrice(30000);
food1.setUser(user1); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food1);
Food food2 = new Food();
food2.setName("์๋ณด์นด๋ ํผ์");
food2.setPrice(50000);
food2.setUser(user1); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food2);
Food food3 = new Food();
food3.setName("ํ๋ผ์ด๋ ์นํจ");
food3.setPrice(15000);
food3.setUser(user1); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food3);
Food food4 = new Food();
food4.setName("์๋
์นํจ");
food4.setPrice(20000);
food4.setUser(user2); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food4);
Food food5 = new Food();
food5.setName("๊ณ ๊ตฌ๋ง ํผ์");
food5.setPrice(30000);
food5.setUser(user2); // ์ธ๋ ํค(์ฐ๊ด ๊ด๊ณ) ์ค์
foodList.add(food5);
foodRepository.saveAll(foodList);
}
}

์ฐ๊ด ๊ด๊ณ ์ ๊ฑฐ
@Test
@Transactional
@Rollback(value = false)
@DisplayName("์ฐ๊ด๊ด๊ณ ์ ๊ฑฐ")
void test1() {
// ๊ณ ๊ฐ Robbie ๋ฅผ ์กฐํํฉ๋๋ค.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// ์ฐ๊ด๋ ์์ Entity ์ ๊ฑฐ : ํ๋ผ์ด๋ ์นํจ
Food chicken = null;
for (Food food : user.getFoodList()) {
if(food.getName().equals("ํ๋ผ์ด๋ ์นํจ")) {
chicken = food;
}
}
// ํ๋ผ์ด๋ ์นํจ์ด ์๋ค๋ฉด, ํด๋นํ๋ chicken ์ ๊ฑฐ
if(chicken != null) {
user.getFoodList().remove(chicken);
}
// ์ฐ๊ด๊ด๊ณ ์ ๊ฑฐ ํ์ธ
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
}


- ์ฐ๊ด ๊ด๊ณ๋ ์ ๊ฑฐ๋์ด ๋์ค์ง๋ง, ์ค์ DB์์๋ ์ ๊ฑฐ๋์ง ์์!
- JPA์์๋ ์ฐ๊ด ๊ด๊ณ๋ฅผ ์ ๊ฑฐํ๋ ๊ฒ๋ง์ผ๋ก๋ ๊ทธ์ ํด๋นํ๋ ๋ฐ์ดํฐ๋ฅผ ์ง์ธ ์ ์๋ ๋ฐฉ๋ฒ์ผ๋ก orphanRemoval ์ต์
์ ๊ณต
- default๋ false
...
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
...
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Food> foodList = new ArrayList<>();
...
}


- ์ฐ๊ด ๊ด๊ณ๋ฅผ ์ ๊ฑฐํ๋ ๊ฒ๋ง์ผ๋ก๋ ํด๋น Entity ์ญ์ ๋จ!
- orphanRemoval ์ต์ ๋ Cascade.DELETE ์ต์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ํด๋น Entity๋ฅผ ์ญ์ ํ๋ฉด ์ฐ๊ด๋ Entity๋ค์ด ์๋์ผ๋ก ์ญ์ ๋จ
โ ๏ธ ์ฃผ์ํ ์
orphanRemoval ์ต์ ๊ณผ Cascade.DELETE ์ต์ ์ ์ฌ์ฉํ๋ ค๊ณ ํ ๋, ์ญ์ ํ๋ ค๊ณ ํ๋ ๊ทธ ์ฐ๊ด๋ Entity๋ฅผ ๋ค๋ฅธ ๊ณณ์์๋ ์ฐธ์กฐํ๊ณ ์๋์ง ์๋์ง๋ฅผ ๊ผญ ํ์ธํด์ผ ํจ!
- ๋ฐ๋ผ์, orphanRemoval ์ต์ ์ @ManyToOne ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ์ ์์