[인프런워밍업스터디_BE_0기] 일곱 번째 과제!
문제 1
여섯번째 과제에서 만들었던 Fruit 기능들을 JPA를 이용하도록 변경해보세요!
오늘의 과제는 어제까지 선생님께 배운 다형성을 활용할 기회다! MemoryRepository를 사용하더라도, MySqlRepository를 사용하더라도 @Primary만 바꿔주면 되도록 변경해두었고, 주말에 미리 예습해두었던 JPA에서 사용할 수 있도록 Repository의 메서드도 맞춰둔 상황! JpaRepository만 만들고 바꿔주면 되겠지?!
[JPA를 적용한 Repository]
public interface FruitJpaRepository extends JpaRepository<Fruit, Long> {
List<Fruit> findAllByName(String name);
@Query("select new com.day4.fruitapp.dto.fruit.model.FruitStat(f.soldYn, sum(f.price)) from Fruit f where f.name = :name group by f.soldYn")
List<FruitStat> getStats(@Param("name") String name);
}
하지만 역시 놓친 부분이 있었으니..
[기존 서비스 코드]
@Service
public class FruitServiceV1 implements FruitService{
// 중략 ...
@Override
public void sellFruit(FruitSellReqeust reqeust) {
Fruit fruit = fruitRepository.findById(reqeust.getId())
.orElseThrow(() -> new FruitNotFoundException("과일 정보를 찾을 수 없습니다."));
if (fruit.isSold()) {
throw new FruitNotFoundException("이미 팔린 과일입니다.");
}
fruitRepository.sell(fruit.getId()); // getStats처럼 만들어야하나...?
}
// 후략 ...
}
팔린 과일에 대한 정보를 업데이트하는 부분인데.. FruitJpaRepository의 getStats 처럼 @Query 를 통해 구현할까도 싶었지만! sell이라는 팔렸다는 표현은 Fruit 도메인에 더 잘어울리는 것 같으니 다음과 같이 변경하자!
@Service
public class FruitServiceV2 implements FruitService {
// 중략 ...
@Override
public void sellFruit(FruitSellReqeust reqeust) {
Fruit fruit = fruitRepository.findById(reqeust.getId())
.orElseThrow(() -> new FruitNotFoundException("과일 정보를 찾을 수 없습니다."));
if (fruit.isSold()) {
throw new FruitNotFoundException("이미 팔린 과일입니다.");
}
fruit.sell();
fruitRepository.save(fruit);
}
// 후략 ...
}
각 api의 요청 결과도 그대로 잘 나온다!
문제 2
특정 과일을 기준으로 우리 가게를 거쳐갔던 과일 개수를 세고 싶다.
예를 들어
1. (1, 사과, 3000원, 판매 O)
2. (2, 바나나, 4000원, 판매X)
3. (3, 사과, 3000원, 판매 O)
와 같은 세 데이터가 있을 때 사과를 기준으로 개수를 센다면 2를 반환한다.
API의 스펙은 다음과 같다.
1. HTTP method:
GET
2. HTTP path:
/api/v1/fruit/count
3. HTTP query:
name=과일이름
4. 예시
GET /api/v1/fruit/count?name=사과
5. HTTP 응답 Body
{
"count" : long
}
6. HTTP 응답 예시
{
"count" : 2
}
이번 요구사항은 간단하다. 바로 구현해보자!
[컨트롤러 클래스]
@RestController
public class FruitController {
// 중략 ...
@GetMapping("/api/v1/fruit/count")
public FruitCountResponse getCount(@RequestParam String name) {
return fruitService.getCount(name);
}
}
[서비스 클래스]
@Service
public class FruitServiceImpl implements FruitService {
// 중략 ...
@Override
public FruitCountResponse getCount(String name) {
return new FruitCountResponse(fruitRepository.countByName(name));
}
}
[리포지토리 클래스]
public interface FruitJpaRepository extends JpaRepository<Fruit, Long> {
List<Fruit> findAllByName(String name);
@Query("select new com.day4.fruitapp.dto.fruit.model.FruitStat(f.soldYn, sum(f.price)) from Fruit f where f.name = :name group by f.soldYn")
List<FruitStat> getStats(@Param("name") String name);
long countByName(String name);
}
DB에 없는 과일의 개수를 요청할 때 예외처리를 할 것인지 고민되었지만 거쳐갔던 과일의 개수를 반환하는 것이 요구사항이니 거쳐갔던 적이 없는 과일은 0을 반환하는 것이 맞는 것 같아 예외처리하지 않았다.
이제 API 요청 결과를 보자!
문제 3
아직 판매되지 않은 특정 금액 이상 혹은 특정 금액 이하의 과일 목록을 받고 싶다.
API 스펙은 다음과 같다.
1. HTTP method:
GET
2. HTTP path:
/api/v1/fruit/list
3. HTTP query:
option=GTE
또는option=LTE
,price=기준 금액
- GTE : greater than equal의 의미
- LTE : less than equal의 의미
4. 예시
GET /api/v1/fruit/count?option=GTE&price=3000
- 판매되지 않은 3000원 이상의 과일 목록을 반환한다.
5. HTTP 응답 Body
[{
"name" : String,
"price" : long,
"warehousingDate" : LocalDate
}, ...]
6. HTTP 응답 예시
[
{
"name" : "사과",
"price" : 4000,
"warehousingDate" : "2024-01-05"
},
{
"name" : "바나나",
"price" : 6000,
"warehousingDate : "2024-01-8"
}
]
이번 요구사항은 조금 복잡하지만 그래도 어렵진 않다! LTE, GTE라는 파라미터를 문자열로 받기 보다는 Enum으로 관리하면 좋을 것 같다.. 바로 구현해보자!
[요청 클래스와 Enum 클래스]
public class NotSoldFruitRequest {
private PriceComparison option;
private long price;
public NotSoldFruitRequest(PriceComparison option, long price) {
this.option = option;
this.price = price;
}
public PriceComparison getOption() {
return option;
}
public long getPrice() {
return price;
}
}
public enum PriceComparison {
GTE,
LTE
}
[컨트롤러 클래스]
@RestController
public class FruitController {
// 중략 ...
@GetMapping("/api/v1/fruit/list")
public List<NotSoldFruitResoponse> getNotSoldFruits(NotSoldFruitRequest request) {
return fruitService.getNotSoldFruits(request);
}
}
[서비스 클래스]
@Service
public class FruitServiceImpl implements FruitService {
// 중략 ...
@Override
public List<NotSoldFruitResoponse> getNotSoldFruits(NotSoldFruitRequest request) {
List<Fruit> fruits = switch (request.getOption()) {
case GTE -> fruitRepository.findAllBySoldYnAndPriceGreaterThanEqualOrderByName(request.getPrice());
case LTE -> fruitRepository.findAllBySoldYnAndPriceLessThanEqualOrderByName(request.getPrice());
};
return fruits.stream().map(NotSoldFruitResoponse::new).collect(Collectors.toList());
}
}
[리포지터리 클래스]
public interface FruitJpaRepository extends JpaRepository<Fruit, Long> {
// 중략 ...
List<Fruit> findAllBySoldYnAndPriceGreaterThanEqualOrderByName(String soldYn, long price);
List<Fruit> findAllBySoldYnAndPriceLessThanEqualOrderByName(String soldYn, long price);
}
"N"을 직접 파라미터로 넘겨주는게 마음에 들지 않지만 시간이 부족하니 오늘은 여기까지..ㅠ
이제 요청해보자
댓글을 작성해보세요.
enum을 어떻게 적용하는지 궁금했었는데 좋은 답이 된 것 같습니다.