워밍업 클럽 - 백엔드 7일차 과제

워밍업 클럽 - 백엔드 7일차 과제

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! 강의 7일차 과제입니다.

(강의 링크)

7일차 과제는 JPA를 이용하는 것이다.

문제 1. 이전의 6일차 과제를 JPA를 이용하도록 바꿔보기

먼저 JPA를 사용하는 서비스를 새로 만들기 위해 FruitService 인터페이스를 만들었다.

public interface FruitService {
    FruitCreateResponse CreateFruit(FruitCreateRequest request);

    FruitSellResponse sellFruit(FruitSellRequest request);

    FruitStatResponse getFruitStat(FruitStatRequest request);
}

그리고 기존의 FruitMySqlRepository에 의존하던 서비스는 FruitServiceV1으로 이름을 바꾸고 FruitServiceV2를 만들었다.

FruitServiceV2.java

@Primary
@Service
public class FruitServiceV2 implements FruitService {
    private final FruitRepository fruitRepository;

    public FruitServiceV2(FruitRepository fruitRepository) {
        this.fruitRepository = fruitRepository;
    }


    @Override
    public FruitCreateResponse CreateFruit(FruitCreateRequest request) {
        Fruit fruit = fruitRepository.save(new Fruit(request.getName(), request.getWarehousingDate(), request.getPrice(), false));
        return new FruitCreateResponse(fruit);
    }

    @Override
    public FruitSellResponse sellFruit(FruitSellRequest request) {
        Fruit fruit = fruitRepository.findById(request.getId()).orElseThrow(() -> new IllegalArgumentException("해당하는 과일이 없습니다."));
        fruit.setIsSold(true);
        fruitRepository.save(fruit);
        return new FruitSellResponse(fruit.getId());
    }

    @Override
    public FruitStatResponse getFruitStat(FruitStatRequest request) {
        List<Fruit> fruits = fruitRepository.findAllByName(request.getName());
        long totalAmount = fruits.stream().mapToLong(Fruit::getPrice).sum();
        long salesAmount = fruits.stream().filter(Fruit::getIsSold).mapToLong(Fruit::getPrice).sum();
        long notSalesAmount = totalAmount - salesAmount;
        return new FruitStatResponse(salesAmount, notSalesAmount);
    }
}

JpaRepository를 위해 만들어서 사용한 FruitRepository는 다음과 같다.

public interface FruitRepository extends JpaRepository<Fruit, Long> {

    List<Fruit> findAllByName(String name);
}

이것으로 간단하게 리팩터링할 수 있었다.

imageimageimage


문제 2. 특정 과일을 기준으로 과일 수 세기

Fruit를 이름으로 가져오도록해서 구현했다.

@Override
public FruitCountResponse getFruitCount(FruitCountRequest request) {
    List<Fruit> fruits = fruitRepository.findAllByName(request.getName());
    return new FruitCountResponse(fruits.size());
}

그리고 SQL문의 카운트를 당연히 지원하지 않을까 싶어서 찾아보았다.

그리고 다시 countByName으로 리팩터링했다

public FruitCountResponse getFruitCount(FruitCountRequest request) {
    long count = fruitRepository.countByName(request.getName());
    return new FruitCountResponse(count);
}

image


문제3. 미판매이며 특정 금액에 관한 범위 이내의 과일 목록 가져오기

option에 따라 다른 쿼리(JPA 메서드)를 사용할 수 있도록했다.

요구사항을 보고 레포지터리에 필요한 Jpa 메서드를 바로 추가했다.

List<Fruit> findAllByPriceGreaterThanEqual(Long price);
List<Fruit> findAllByPriceLessThanEqual(Long price);

다음의 메서드를 이용해서 Fruit 서비스에 getFruitList 메서드를 추가로 구현했다.

FruitServiceV2.java

@Override
public List<FruitListResponse> getFruitList(FruitListRequest request) {
    String option = request.getOption();
    List<FruitListResponse> fruitListResponses = new ArrayList<>();
    List<Fruit> fruits;
    if (option.equals("GTE")) {
        fruits = fruitRepository.findAllByPriceGreaterThanEqual(request.getPrice());
    } else if (option.equals("LTE")) {
        fruits = fruitRepository.findAllByPriceLessThanEqual(request.getPrice());
    } else {
        throw new IllegalArgumentException("잘못된 옵션입니다.");
    }

    for (Fruit fruit : fruits.stream().filter(fruit -> !fruit.getIsSold()).toList()) {
        fruitListResponses.add(new FruitListResponse(fruit));
    }

    return fruitListResponses;
}

실행 결과는 다음과 같다.

현재 데이터를 보면 2000 가격 이하로 안 팔린 사과는 1월 20일 창고날짜로 두개가 나와야 한다.

MySql Fruit 테이블

image

요청 결과를 확인해보니 잘 나왔다.

image하지만 굳이 스트림 API로 안 팔린 과일을 필터링 할 필요가 없었다.

SQL WHERE 조건을 어떻게 Jpa에서 하는지 추가로 찾아보아서 개선했다.

그래서 수정된 새로운 FruitRepository는 다음과 같다.

public interface FruitRepository extends JpaRepository<Fruit, Long> {

    List<Fruit> findAllByName(String name);

    long countByName(String name);

    //    List<Fruit> findAllByPriceGreaterThanEqual(Long price); <- 이전
    List<Fruit> findAllByPriceGreaterThanEqualAndIsSoldFalse(Long price);

    //    List<Fruit> findAllByPriceLessThanEqual(Long price); <- 이전
    List<Fruit> findAllByPriceLessThanEqualAndIsSoldFalse(Long price);
}

AndIsSoldFalse로 안 팔린 것만 가져오도록 했다.

호출해서 확인해본 hibernate가 생성하는 쿼리이다.

imageWHERE NOT(F1.is_sold)로 알맞게 WHERE문으로 추가되었다.

 

쓸모없는 stream.filter가 사라진 서비스의 코드는 다음과 같다.

@Override
public List<FruitListResponse> getFruitList(FruitListRequest request) {
    String option = request.getOption();
    List<FruitListResponse> fruitListResponses = new ArrayList<>();
    List<Fruit> fruits;
    if (option.equals("GTE")) {
        fruits = fruitRepository.findAllByPriceGreaterThanEqualAndIsSoldFalse(request.getPrice());
    } else if (option.equals("LTE")) {
        fruits = fruitRepository.findAllByPriceLessThanEqualAndIsSoldFalse(request.getPrice());
    } else {
        throw new IllegalArgumentException("잘못된 옵션입니다.");
    }

    for (Fruit fruit : fruits) {
        fruitListResponses.add(new FruitListResponse(fruit));
    }

    return fruitListResponses;
}

실행 결과는 동일했다. 결과는 같지만 내부적으로는 서버에서 필터링하지 않고 데이터베이스에서 필터링하여 더 작은 데이터가 오가게 되었다.

image

GTE 옵션도 테스트해보았다.

image

잘 된다 😀

댓글을 작성해보세요.

채널톡 아이콘