[인프런 워밍업 클럽] 과제4

Jiwon·2024년 5월 6일
0
post-thumbnail

📚 인프런 강의와 스터디를 바탕으로 한 과제를 풀이한 포스트입니다.

과일 가게 운영을 표현하는 API를 만드는 과제다.
강의 상에서는 Controller-Service-Repository로 분리하지 않았지만, 나는 강의 수강 과정에서 이미 분리 내용을 배워서 기존 작업 코드도 분리되어 있는 상태였기에 분리해서 코드를 작성했다.

<fruit 테이블 명세>

create table fruit ( 
	id bigint auto_increment,
	name varchar(20), 
    warehousingDate date,
	price bigint,
    purchase boolean default false,
    primary key (id)
); 

<문제1> 과일 정보 저장하기

  1. FruitController
@RestController
public class FruitController {

    private final FruitService fruitService;
    public FruitController(JdbcTemplate jdbcTemplate) {
        this.fruitService = new FruitService(jdbcTemplate);
    }

    @PostMapping("/api/v1/fruit") // // GET /api/v1/fruit
    public void saveFruit (@RequestBody FruitCreateRequest request)
    {
        fruitService.saveFruit(request);
    }
  • @RestController 어노테이션 표기를 안해서 자꾸 오류가 생겼었다. 어노테이션 표기의 중요성을 깨달았다.

** FruitCreateRequest

public class FruitCreateRequest {
    private String name;
    private LocalDate warehousingDate;
    private long price;

    public String getName() {
        return name;
    }

    public LocalDate getWarehousingDate() {
        return warehousingDate;
    }

    public long getPrice() {
        return price;
    }
}
  1. FruitService
public class FruitService {
    private final FruitRepository fruitRepository;

    public FruitService(JdbcTemplate jdbcTemplate) {
        fruitRepository = new FruitRepository(jdbcTemplate);
    }

    // C
    public void saveFruit(FruitCreateRequest request) {
        fruitRepository.saveFruit(request.getName(), request.getWarehousingDate(), request.getPrice());
    }
  • request 요청 객체로 name, warehousingDate, price를 받아 온다.
  1. FruitRepository
public class FruitRepository {
    private final JdbcTemplate jdbcTemplate;

    public FruitRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void saveFruit(String name, LocalDate warehousingDate, long price) {
        String sql = "INSERT INTO fruit(name, warehousingDate, price) VALUES(?, ?, ?)";
        jdbcTemplate.update(sql, name, warehousingDate, price);
    }
  • jdbcTemplate.update를 INSERT 쿼리에 사용했다.

<실행 결과>

  • 미리 넣어둔 값 밑에 새로 INSERT 된 것을 볼 수 있다.

<한 걸음 더>

  • 자료형 int에는 정수 범위에 한계가 있어 이를 초과하는 범위를 표현하기 위해 long을 사용했다.

<문제 2> 과일이 판매되었다면 팔렸다고 기록하기
purchase의 default 값을 false(0)으로 지정해두었는데, id를 입력받아 해당하는 행의 purchase 값을 true(1)로 UPDATE 해준다.

  1. FruitController
 @PutMapping("/api/v1/fruit") // PUT //api/v1/fruit
    public void updateFruit (@RequestBody FruitUpdateRequest request)
    {
        fruitService.updateFruit(request);
    }
  • Postman의 Body로 RequestBody를 넘겨준다.

** FruitUpdateRequest

public class FruitUpdateRequest {
    private long id;

    public long getId() {
        return id;
    }
}
  1. FruitService
// U
    public void updateFruit(FruitUpdateRequest request) {
        fruitRepository.updateFruit(request.getId());
    }
  • request 요청 객체로 id를 받아 온다.
  1. FruitRepository
public void updateFruit(long id) {
        String Readsql = "SELECT * FROM fruit WHERE id = ?";
        boolean isIdNotExist = jdbcTemplate.query(Readsql, (rs, rowNum) -> 0, id).isEmpty();
        if (isIdNotExist) {
            throw new IllegalArgumentException();
        }

        String sql = "UPDATE fruit SET purchase = true WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }
  • 존재하지 않는 id가 요청된 경우 예외 처리를 해주었다.
  • jdbcTemplate.update를 UPDATE 쿼리에 사용했다.

<실행 결과>

  • 임의로 다음과 같이 fruits 테이블에 데이터를 넣었다.

  • Postman으로 HTTP 요청 Body를 보내면 200 OK라는 표시가 뜬다.

  • 실제 fruit 테이블을 select해 보면 다음과 같이 purchase가 1로 update된 것을 알 수 있다.

<문제 3> 특정 과일을 기준으로 팔린 금액, 팔리지 않은 금액 조회하기

  1. FruitController
@GetMapping("/api/v1/fruit/stat")
    public FruitResponse getFruit(@RequestParam String name) {
        return fruitService.getFruit(name);
    }
  • HTTP 응답 바디가 있기 때문에 salesAmountnotSaleAmount 변수 정보를 가질 FruitResponse 클래스를 만들었다.

** FruitResponse 클래스

public class FruitResponse {
    private long salesAmount;
    private long notSalesAmount;

    public FruitResponse(long salesAmount, long notSalesAmount) {
        this.salesAmount = salesAmount;
        this.notSalesAmount = notSalesAmount;
    }

    public long getSalesAmount() {
        return salesAmount;
    }

    public long getNotSalesAmount() {
        return notSalesAmount;
    }
}
  1. FruitService
// R
    public FruitResponse getFruit(String name) {
        return fruitRepository.getFruit(name);
    }
  • @RequestParam으로 받는 name을 넘겨주게 된다.
  1. FruitRepository
 public FruitResponse getFruit(String name) {
        String sql = "SELECT * FROM fruit WHERE name = ?";
        boolean isFruitNotExist = jdbcTemplate.query(sql, (rs, rowNum) -> 0, name).isEmpty();
        if (isFruitNotExist) {
            throw new IllegalArgumentException();
        }

        long salesAmount = 0;
        long notSalesAmount = 0;

        List<Fruit> responses = jdbcTemplate.query(sql, (rs, rowNum) -> {
            boolean purchase = rs.getBoolean("purchase");
            long price = rs.getLong("price");
            return new Fruit(purchase, price);
        }, name);

        for (Fruit response : responses) {
            if (response.isPurchase()) {
                salesAmount += response.getPrice();
            }
            else {
                notSalesAmount += response.getPrice();
            }
        }

        return new FruitResponse(salesAmount, notSalesAmount);
    }
  • 존재하지 않는 name이 요청된 경우 예외 처리를 해주었다.
  • salesAmount와 notSalesAmount는 sql문으로 뽑아내는 것이 아니라 클래스 내부에서 변수를 만들어 purchase의 값에 따라 더해주었다. (*처음에 sql로 시도하다가 계속 풀리지 않아서 구글링을 통해 참고했다.)
  • name에 해당하는 행들만 select하고 그중에서 필요한 것은 purchaseprice이기 때문에 이 정보만 가지고 있을 클래스 Fruit을 만들어 해당하는 값들을 리스트로 만들었다.

** Fruit 클래스

public class Fruit {
    private boolean purchase;
    private long price;

    public boolean isPurchase() {
        return purchase;
    }

    public long getPrice() {
        return price;
    }

    public Fruit(boolean purchase, long price) {
        this.purchase = purchase;
        this.price = price;
    }
}
  • Fruit 리스트에 있는 각 요소들을 for문으로 돌면서 isPurchase()가 1이면(즉 팔린 과일이라면) salesAmount에 더하고, 그렇지 않으면 notSalesAmount에 더한다. 그 후 FruitResponse의 생성자를 return하면 된다.

<한 걸음 더>

  • sum, group by를 이용해서도 같은 결과를 출력할 수 있다.

  • FruitRepository

public FruitResponse getFruit(String name) {
        String sql = "SELECT * FROM fruit WHERE name = ?";
        boolean isFruitNotExist = jdbcTemplate.query(sql, (rs, rowNum) -> 0, name).isEmpty();
        if (isFruitNotExist) {
            throw new IllegalArgumentException();
        }

        long salesAmount = 0;
        long notSalesAmount = 0;


        String sumsql = "SELECT purchase, sum(price) as total_price\n" +
                "FROM fruit\n" +
                "WHERE name = ? GROUP BY purchase";

        List<FruitGroupby> responses = jdbcTemplate.query(sumsql, (rs, rowNum) -> {
            boolean purchase = rs.getBoolean("purchase");
            long total_price = rs.getLong("total_price");
            return new FruitGroupby(purchase, total_price);
        }, name);

        for (FruitGroupby response : responses) {
            if (response.isPurchase()) {
                salesAmount += response.getTotal_price();
            }
            else {
                notSalesAmount += response.getTotal_price();
            }
        }

        return new FruitResponse(salesAmount, notSalesAmount);
}
  • 기존의 코드와 다른 점은 SELECT sql 입력한 name에 해당하는 행들만 골라서 purchase를 기준으로 price의 합을 구하는 것으로 작성했다는 것이다.

    SQL문을 DB에서 실행하면 이렇게 나온다.

  • groupby로 name에 해당하는 행이 각각 출력된 것이 아니어서 purchasetotal_price 정보를 담을 FruitGroupby 클래스를 새로 만들었다.
    ** FruitGroupby 클래스

public class FruitGroupby {
    private boolean purchase;
    private long total_price;

    public boolean isPurchase() {
        return purchase;
    }

    public long getTotal_price() {
        return total_price;
    }

    public FruitGroupby(boolean purchase, long total_price) {
        this.purchase = purchase;
        this.total_price = total_price;
    }
}
  • FruitGroupby 리스트에 있는 각 요소들을 for문으로 돌면서 isPurchase()가 1이면(즉 팔린 과일이라면) salesAmount에 더하고, 그렇지 않으면 notSalesAmount에 더한다. 그 후 FruitResponse의 생성자를 return하면 된다.

<실행 결과>

  • 문제에서 주어진 것을 바탕으로 미리 table에 데이터를 insert 해주었다.

  • Postman으로 확인해 보면 잘 작동하고 있는 것을 볼 수 있다.

  • 정리해 보면, HTTP 응답 바디를 받을 때는 필요한 변수들을 가지고 있는 클래스들을 새로 정의해서(이 문제에서는 Fruit 클래스나 FruitGroupby 클래스가 해당) 여기에 sql의 실행 결과로 나온 row 중 필요한 column들만 assign 해줘야 한다.

  • 그리고 for문을 돌면서 각 row들을 검사하고, 조건에 맞다면 더해준다. 최종적으로는 FruitResponse라는 salesAmountnotSalesAmount를 가진 클래스의 생성자를 return해서 문제에서 제시된 것과 같은 결과를 얻을 수 있다.

  • 또한 구매 여부를 표시하는 purchase의 자료형을 처음에는varchar로 했었는데, 판매 여부를 판단할 때는 boolean을 사용하는 것이 훨씬 편하고, 적합한 자료형이다.

[Reference]

profile
écoute les murmures du cœur

0개의 댓글

관련 채용 정보