고을마을 : 나의 코딩 이야기

항해99 27일차 TIL[@ManyToOne, @JoinColumn, .stream().forEach() ] 본문

항해99 7기/TIL(Today I Learned)

항해99 27일차 TIL[@ManyToOne, @JoinColumn, .stream().forEach() ]

고을마을 2022. 6. 8. 02:22

2022년 6월 7일. 항해 27일차.

주특기 심화 주차이고 과제에 전념했던 날이었다.

솔직히 내 코드가 맞는 건지도 잘 모르겠지만 테스트를 돌려보면 통과된다!


다음은 과제의 일부이다.

 

  • 음식점 정보 입력받아 등록
    1. 음식점 이름 (name)
    2. 최소주문 가격 (minOrderPrice)
      1. 허용값: 1,000원 ~ 100,000원 입력
      2. 100 원 단위로만 입력 가능 (예. 2,220원 입력 시 에러발생. 2,300원은 입력 가능)
      3. 허용값이 아니거나 100원 단위 입력이 아닌 경우 에러 발생시킴
    3. 기본 배달비 (deliveryFee)
      1. 허용값: 0원 ~ 10,000원 (예. 11,000원 입력 시 에러발생.)
      2. 500 원 단위로만 입력 가능 (예. 2,200원 입력 시 에러발생. 2,500원 입력 가능)
  • 음식점 조회
    • 등록된 모든 음식점 정보 조회 가능
      1. 등록 시 입력한 음식점 정보 (name, minOrderPrice, deliveryFee)
      2. DB 테이블 ID (id)

 

사실 1번 문제는 쉽게 할 수 있었다.

일단 Entity를 구상해봤다.

@AllArgsConstructor // @Builder 사용할때 필요함.
@NoArgsConstructor
@Getter
@Entity
@Builder
public class Food {

    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private int price;

    @ManyToOne
    @JoinColumn(name = "restaurant_id")
    private Restaurant restaurant;


    public Food(FoodDto foodDto){
        this.id = foodDto.getRestaurantId();
        this.name = foodDto.getName();
        this.price = foodDto.getPrice();

    }
}

 

이를 토대로 컨트롤러, 레포지토리, 컨트롤러를 쉽게 만들어볼수 있었던 것 같다. 

 public Restaurant registerRestaurant(RestaurantDto restaurantDto){
        Restaurant restaurant = new Restaurant(restaurantDto);

        if(restaurantDto.getMinOrderPrice() < 1000
                || restaurantDto.getMinOrderPrice() > 100000){
            throw new IllegalArgumentException("최소주문 허용가격이 아닙니다.(허용가격은 1,000원 ~ 100,000원)");
        }

        if(restaurantDto.getMinOrderPrice() % 100 != 0){
            throw new IllegalArgumentException("100원 단위 입력값이 아닙니다.");
        }

        if(restaurantDto.getDeliveryFee() <0 || restaurantDto.getDeliveryFee() > 10000){
            throw new IllegalArgumentException("기본배달비 허용 값이 아닙니다.(허용값: 0원 ~ 10,000원)");
        }

        if(restaurantDto.getDeliveryFee() % 500 != 0){
            throw new IllegalArgumentException("500원 단위 입력값이 아닙니다.");
        }

        restaurantRepository.save(restaurant);
        return restaurant;
    }

    public List<Restaurant> getRestaurantList(){
        return restaurantRepository.findAll();
    }
}

 

여기까지는 쉽게 끝낼 수 있었지만...

 

2번 문제의 난관이 기다리고 있었으니...

 

일단 Food Entity를 만들고 음식점 ID를 등록해줘야만 했다.

 

@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "restaurant_id")
private Restaurant restaurant;

처음에 Food에도 별도의 인조키를 두어야 하는지부터 꼬이기 시작했었는데,

Entity에 ID가 없으면 안되기에 넣어주게 됐고

Restaurant 여러개에 음식은 하나씩 넣어줘야 하기때문에 @ManyToOne을 넣어주게 됐다.

Restaurant id를 가지고 와야하기 때문에 @JoinColumn까지 넣어줬다. 

 

 

@Transactional
public void registerFood(Long restaurantId, List<FoodDto> foodDtoList){

    Restaurant restaurant = restaurantRepository.findById(restaurantId).orElseThrow(
            () -> new NullPointerException("음식점 id가 존재하지 않음")
    );

    foodDtoList.stream().forEach(foodDto -> {

        if(foodDto.getPrice() < 100 || foodDto.getPrice() > 1000000 ){
            throw new IllegalArgumentException("음식 가격 범위가 아닙니다(가격 범위 : 100원 ~ 1,000,000원 입니다.)");
        }

        if(foodDto.getPrice() % 100 != 0){
            throw new IllegalArgumentException("100원 단위 입력이 필요합니다.");
        }

        Optional<Food> food = foodRepository.findFoodByRestaurantAndName(restaurant, foodDto.getName());
        if(food.isPresent()){
            throw new IllegalArgumentException("같은 음식점 내에서는 음식 이름이 중복될 수 없습니다.");
        }

        foodDto.setRestaurantId(restaurantId);
        foodRepository.save(foodDto.permit(restaurant));
    });

 

위에는 서비스 부분인데. stream().forEach() 사용법이 무척이나 헷갈렸다.

foodDtoList에 있는 내용들을 하나씩 봐줘야하는데 도무지 감이 잡히지 않았고 구글링을 엄청나게 했던 것 같다.

 

https://coding-factory.tistory.com/574

 

[Java] 자바 스트림(Stream) 사용법 & 예제

스트림(Stream)이란? 스트림은 자바8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다. Iterator와 비슷한 역할을 하지만 람다식으로 요소 처리

coding-factory.tistory.com

 

해당 태그의 블로그를 확인해 봄으로써 List<>에서의 stream(), forEach()을 이해하고 활용해볼 수 있었다.

 

Optional<Food> food = foodRepository.findFoodByRestaurantAndName(restaurant, foodDto.getName());
if(food.isPresent()){
    throw new IllegalArgumentException("같은 음식점 내에서는 음식 이름이 중복될 수 없습니다.");
}

같은 음식점 내에서 음식 이름이 중복될 수 없음을 나타내는 IllegalArgumentException을 만들어 줘야했는데...

여기서도 난관이었던 것 같다. 

중복을 어떻게 확인하면 될지 구글링해봤지만... 구글링 능력이 아직 부족한지 실패...

 

문득 로그인 기능을 공부하면서 중복 ID를 확인할때 사용했던 Optional<> isPresent()가 생각났다. 

코드를 붙여넣고 약간의 수정을 해줬더니 정상적으로 작동...! 

 

foodDto.setRestaurantId(restaurantId);
foodRepository.save(foodDto.permit(restaurant));

.permit()은

 

public Food permit(Restaurant restaurant){
    return Food.builder()
            .restaurant(restaurant)
            .name(this.name)
            .price(this.price)
            .build();

FoodDto에 있던 함수였는데... 여기에 bulilder()가 포함되어 있다.

builder()를 통해서, restaurant의 ID, name, price를 넣고, 객체를 만들어주고, save 한 것!

 

 

테스트도 잘 통과됐다ㅠㅠ