본문 바로가기
스터디공부/인프런 워밍업

인프런 워밍업 스터디 일곱 번째 과제! (진도표 7일차)

by 파덕 2024. 2. 27.

출처 : 자바-스프링부트-서버개발-올인원

문제 1

JpaRepository

public interface FruitRepositoryJpa extends JpaRepository<Fruit, Long> {

    List<Fruit> findAllByName(String name);

    long countByName(String name);

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

JpaRepositoryImp

@Primary
@Repository
public class FruitRepositoryJpaImp implements FruitRepositoryInterface {

    private final FruitRepositoryJpa repositoryJpa;

    public FruitRepositoryJpaImp(FruitRepositoryJpa repositoryJpa) {
        this.repositoryJpa = repositoryJpa;
    }

    @Override
    public void save(String name, LocalDate warehousingDate, long price) {
        Fruit fruit = new Fruit(name, price,null,"HAVING");
        repositoryJpa.save(fruit);
    }

    @Override
    public void updateSellStatus(long id) {
        Fruit fruit = repositoryJpa.findById(id).orElseThrow(() -> new IllegalArgumentException("데이터 없음"));
        fruit.selling();
        repositoryJpa.save(fruit);
    }

    @Override
    public List<Fruit> getSalesInfo(String fruitName) {
        return repositoryJpa.findAllByName(fruitName);
    }

    @Override
    public long getAmountBy(String name) {
        return repositoryJpa.countByName(name);
    }

    @Override
    public List<Fruit> getFruitLte(Long price) {
        return repositoryJpa.findAllByPriceLessThanEqual(price);
    }

    @Override
    public List<Fruit> getFruitGte(Long price) {
        return repositoryJpa.findAllByPriceGreaterThanEqual(price);
    }
}

FruitRepositoryInterface

public interface FruitRepositoryInterface {

    void save(String name, LocalDate warehousingDate, long price);
    void updateSellStatus(long id) ;
    List<Fruit> getSalesInfo(String fruitName);
    long getAmountBy(String name);

    List<Fruit> getFruitLte(Long price);

    List<Fruit> getFruitGte(Long price);
}

FruitRepositoryInterface를 구현한 클래스를 사용한 이유
스프링 컨테이너를 활용하여 기존 코드를 수정하지 않고 동작하기 위해서 사용했습니다.
그리고 Repository가 변경되지 않는다면 상관없지만 데이터 접속 방법을 Jpa에 너무 의존하는 방식보다
JpaRepository Bean을 주입받아서 사용하는 것이 테스트 코드를 작성하거나
추후에 다른 Jpa가 아닌 ORM이나 다른 방식을 사용해도 변경이 쉬울거라 생각됩니다.

문제 2

controller

    @GetMapping("/api/v1/fruit/count")
    public FruitAmountResponse getFruitAmountBy(@RequestParam("name") String name) {
        Assert.hasText(name,"이름은 필수입니다.");
        return fruitService.getAmountBy(name);
    }

    public static class FruitAmountResponse {
        private final long count;

        public FruitAmountResponse(long amount) {
            count = amount;
        }

//        public FruitAmountResponse() {
//        }

        public long getCount() {
            return count;
        }
    }

service

   public FruitController.FruitAmountResponse getAmountBy(String name) {
        long amount = fruitRepository.getAmountBy(name);
        return new FruitController.FruitAmountResponse(amount);
    }

repository

@Primary
@Repository
public class FruitRepositoryJpaImp implements FruitRepositoryInterface {

    private final FruitRepositoryJpa repositoryJpa;

    public FruitRepositoryJpaImp(FruitRepositoryJpa repositoryJpa) {
        this.repositoryJpa = repositoryJpa;
    }
    @Override
    public long getAmountBy(String name) {
        return repositoryJpa.countByName(name);
    }
}


public interface FruitRepositoryJpa extends JpaRepository<Fruit, Long> {

    long countByName(String name);
}

문제 3

controller


    @GetMapping("/api/v1/fruit/list")
    public List<FruitHavingResponse> getHavingFruitByOption(FruitHavingRequest request) {
        return fruitService.getHavingFruitByOption(request.getOption(),request.getPrice());
    }
    public static class FruitHavingRequest {
        private String option;
        private Long price;

        public FruitHavingRequest() {
        }

        public String getOption() {
            return option;
        }

        public Long getPrice() {
            return price;
        }
//
        public void setOption(String option) {
            Assert.hasText(option,"값이 없으면 안됩니다.");
            this.option = option;
        }

        public void setPrice(Long price) {
            Assert.notNull(price,"값이 없으면 안됩니다.");
            this.price = price;
        }

        @Override
        public String toString() {
            return "FruitHavingRequest{" +
                    "option='" + option + '\'' +
                    ", price=" + price +
                    '}';
        }
    }

    public static class FruitHavingResponse {
        private String name;
        private long price;
        private LocalDate warehousingDate;

        public FruitHavingResponse(String name, long price, LocalDate warehousingDate) {
            this.name = name;
            this.price = price;
            this.warehousingDate = warehousingDate;
        }

        public FruitHavingResponse() {
        }

        public FruitHavingResponse(Fruit fruit) {
            this.name = fruit.getName();
            this.warehousingDate = fruit.getWarehousingDate();
            this.price = fruit.getPrice();
        }

        public String getName() {
            return name;
        }

        public long getPrice() {
            return price;
        }

        public LocalDate getWarehousingDate() {
            return warehousingDate;
        }
    }

service

    public List<FruitController.FruitHavingResponse> getHavingFruitByOption(String option, Long price) {
        isVerify(option);
        isVerify(price);

        List<Fruit> fruits = null;
        if (option.equals("LTE")) {
            fruits = fruitRepository.getFruitLte(price);
        } else {
            fruits = fruitRepository.getFruitGte(price);
        }

        return fruits.stream().map(FruitController.FruitHavingResponse::new).collect(Collectors.toList());
    }

    private void isVerify(String option) {
        List<String> options = List.of("LTE", "GTE");
        boolean isVerifyOption = options.contains(option);
        if (!isVerifyOption) {
            throw new IllegalArgumentException("값 확인");
        }
    }

    private void isVerify(Long price) {
        var isVerifyOption = price >= 0L;
        if (!isVerifyOption) {
            throw new IllegalArgumentException("값 확인");
        }
    }

repository

@Primary
@Repository
public class FruitRepositoryJpaImp implements FruitRepositoryInterface {

    private final FruitRepositoryJpa repositoryJpa;

    public FruitRepositoryJpaImp(FruitRepositoryJpa repositoryJpa) {
        this.repositoryJpa = repositoryJpa;
    }

    @Override
    public List<Fruit> getFruitLte(Long price) {
        return repositoryJpa.findAllByPriceLessThanEqual(price);
    }

    @Override
    public List<Fruit> getFruitGte(Long price) {
        return repositoryJpa.findAllByPriceGreaterThanEqual(price);
    }
}

후기

일부로 DTO 클래스를 Controller의 정적 멤버 클래스로 유지한 상태로 코드를 작성해봤습니다.
그 이유는 Controller 계층에 있는 DTO가 service 계층까지 사용하지 않는게 좋다는걸 몸소 체험하고 싶었습니다.

그리고 계층마다 사용하는 DTO를 분리해서 사용하는게 더 적합하다고 생각이 들었습니다.
검증이나 비즈니스적으로 해당 서비스 메소드가 가진 로직을 DTO에 넣어서 적용할 수 있고
간단한 로직도 포함시키면 코드 작성에 편할거라 생각이 들었습니다.