연관관계를 가진 엔티티 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 |