연관관계를 가진 엔티티 save 하기
insert하려는 테이블이 여러 테이블과 연관관계를 가지고 있을 때 어떻게 해야 할까? Recipe를 추가할 때, 재료는 Ingredient에, 요리 과정은 CookingMethod 테이블에 보관해야 한다. 유형이나 분류는 이미 정해져있는 것을 가져다 사용하면 되므로 제외했다. 프로세스를 짜보자. 사용자 입장에서.
- 등록하려는 레시피가 어떤 유형인지 선택한다.
- 등록하려는 레시피가 어떤 분류인지 선택한다.
- 요리 이름
- 완성된 요리 썸네일 삽입
- 간략한 설명
- 재료를 입력.(여러 개)
- 요리 과정 입력.(여러 개)
- 요리 과정 이미지 삽입(여러 개)
이렇게 입력하고 등록했을 때 서버에서 일어나는 일은
- 입력 받은 데이터를 분류한다.
- Ingredient를 등록한다.
- CookingMethod를 등록한다.
- 2, 3번 데이터를 가지고 Recipe를 등록한다.
이 세가지 과정을 순차적으로 진행하면 될 것 같다. 그런데 매번 세 엔티티를 save해주는 것은 번거롭다. CasCade
를 ALL
으로 설정하면 Recipe를 등록할 때 자동으로 Ingredient와 CookingMethod까지 저장할 수 있을 것 같다.
JPA에서 엔티티를 save할 때, 연관관계를 가진 모든 엔티티는 영속 상태여야 한다. 따라서 Recipe를 저장하는 시점에 Ingredient와 CookingMethods는 이미 영속 상태여야 한다. 엔티티를 save할 때 연관된 엔티티도 함께 save하고 싶다면 영속성 전이 기능인 CASCADE를 이용한다.
CascadeType에 몇 가지 종류가 있는데 ALL을 사용하면 모든 부분에 적용된다.
Recipe.java
@Entity
public class Recipe {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(columnDefinition = "varchar(255) default ''")
private String description;
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Set<Ingredient> ingredients = new HashSet<>();
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OrderBy("sequence ASC")
Set<CookingMethod> cookingMethods = new HashSet<>();
@ManyToOne(fetch = FetchType.LAZY)
private FoodNation foodNation; //한중일양식
@ManyToOne(fetch = FetchType.LAZY)
private FoodType foodType; //국, 반찬, 찌개...
@Column(columnDefinition = "integer default 0")
private Integer cookingTime;
@Column(columnDefinition = "integer default 0")
private int calorie;
@Column(columnDefinition = "integer default 0")
private int servings;
@Column(columnDefinition = "varchar(255) default ''")
private String thumbnail;
Test
@DisplayName("레시피 등록 스트")
@Test
void testRecipeSave() throws Exception {
//레시피 등록 시점에 이미 존재하고 있어야 하는 데이터들 등록
FoodType foodType = new FoodType("test food type");
FoodNation foodNation = new FoodNation("test nation");
IngredientType ingredientType = new IngredientType("test ingredient type");
ingredientTypeRepository.save(ingredientType);
foodNationRepository.save(foodNation);
foodTypeRepository.save(foodType);
List<FoodNation> allNation = foodNationRepository.findAll();
List<FoodType> allTypes = foodTypeRepository.findAll();
List<IngredientType> allIngredientTypes = ingredientTypeRepository.findAll();
//Recipe를 등록할 때 함께 등록할 연관 엔티티 컬렉션
Set<Ingredient> ingredients = new HashSet<>();
Set<CookingMethod> cookingMethods = new HashSet<>();
for (int i=0; i < 10; i++) { //더미 데이터
Ingredient ingredient = Ingredient.builder()
.ingredient(i+"-test")
.quantity(i+"g")
.ingredientType(allIngredientTypes.get(0))
.build();
CookingMethod cookingMethod = CookingMethod.builder()
.description(i+" method des")
.image(i+" method Image")
.sequence(i)
.build();
ingredients.add(ingredient);
cookingMethods.add(cookingMethod);
}
//화면에서 요청하는 form
RecipeSaveForm recipeSaveForm = new RecipeSaveForm();
recipeSaveForm.setName("Test Recipe!!");
recipeSaveForm.setDescription("Test Description");
recipeSaveForm.setThumbnail("image URL");
recipeSaveForm.setCookingTime(20);
recipeSaveForm.setServings(2);
recipeSaveForm.setFoodType(allTypes.get(0));
recipeSaveForm.setFoodNation(allNation.get(0));
recipeSaveForm.setIngredients(ingredients);
recipeSaveForm.setCookingMethods(cookingMethods);
//JSON으로 변경
String requesetJson = objectMapper.writeValueAsString(recipeSaveForm);
System.out.println("requesetJson : " + requesetJson);
//등록 요청
mockMvc.perform(post("/register")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.with(csrf())
.content(requesetJson))
.andExpect(status().isOk());
//검증
List<Recipe> recipes = recipeRepository.findAll();
System.out.println(recipes.get(0).getCookingMethods().isEmpty());
recipes.get(0).getCookingMethods().forEach(row -> System.out.println(row.getDescription()));
assertTrue(!recipes.get(0).getCookingMethods().isEmpty());
assertTrue(!recipes.get(0).getIngredients().isEmpty());
assertEquals(recipes.get(0).getName(), recipeSaveForm.getName());
?
Cascade설정을 하지 않으면 예외가 나지 않을까 싶어서 빼봤는데 정상 작동한다. 왜일까? 위 테스트 코드에서 Ingredient와 CookingMethod는 Trensient상태라고 생각했는데 내가 잘못 알고 있는 걸까?...
'JPA' 카테고리의 다른 글
객체지향 모델과 관계형 모델의 패러다임의 불일치 (0) | 2021.04.26 |
---|---|
[JPA] @EntityGraph를 OnetoMany에 적용 시 페이징 처리 안 되는 이슈 (1) | 2021.01.14 |
JPA 연관관계 매핑 (0) | 2020.11.14 |