인프런 워밍업 클럽 백엔드 - 네 번째 과제

인프런 워밍업 클럽 백엔드 - 네 번째 과제

문제 1

우리는 작은 과일 가게를 운영하고 있습니다. 과일 가게에 입고된 "과일 정보"를 저장하는 API를 만들어 봅시다.
스펙은 다음과 같습니다.

  • HTTP method : POST

  • HTTP path : /api/v1/fruit

  • HTTP 요청 Body

{
  "name": String,
  "warehousingDate": LocalDate,
  "price": long
}
  • HTTP 요청 Body 예시

{
  "name": "사과",
  "warehousingDate": "2024-02-01",
  "price": 5000
}
  • 응답 : 성공 시 200


"과일 정보"를 저장하기 위해서 테이블을 만들었습니다.

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

'stat'은 과일의 판매 여부입니다. 팔린 과일이면 true(1), 팔리지 않은 과일이면 false(0) 값이 입력됩니다. 처음에는 팔리지 않은 과일이기 때문에 기본값을 false로 입력했습니다.

 

Controller

@RestController
public class FruitController {

    private final JdbcTemplate jdbcTemplate;

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

    @PostMapping("/api/v1/fruit")
    public void warehousing(@RequestBody FruitRequest request) {
        String sql = "INSERT INTO fruit (name, warehousingDate, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
    }
}

 

Request

package com.example.task.dto.fruit.request;

import java.time.LocalDate;

public class FruitRequest {

    private String name;
    private LocalDate warehousingDate;
    private long price;

    public String getName() {
        return name;
    }

    public LocalDate getWarehousingDate() {
        return warehousingDate;
    }

    public long getPrice() {
        return price;
    }
}

결과

imageimage응답 성공 시 200 OK와 테이블에 정상적으로 삽입됩니다.

 

한 걸음 더!

자바에서 정수를 다루는 가장 대표적인 두 가지 방법은 intlong입니다.
이 두 가지 방법 중 위 API에서 long을 사용한 이유는 무엇일까요?

int는 32비트를 사용하여 정수를 표현하며, long은 64비트를 사용합니다. 따라서 longint보다 훨씬 큰 정수를 표현할 수 있습니다.

 

 

문제 2

과일이 팔리게 되면, 우리 시스템에 팔린 과일 정보를 기록해야 합니다. 스펙은 다음과 같습니다.

  • HTTP method : PUT

  • HTTP path : /api/v1/fruit

  • HTTP 요청 Body

{
  "id": long
}
  • HTTP 요청 Body 예시

{
  "id": 3
}
  • 응답 : 성공 시 200


Controller

@PutMapping("/api/v1/fruit")
public void sold(@RequestBody FruitRequest request) {
    String readSql = "SELECT * FROM fruit WHERE id = ?";
    boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
    if (isFruitNotExist) {
        throw new IllegalArgumentException();
    }

    String sql = "UPDATE fruit SET stat = true WHERE id = ?";
    jdbcTemplate.update(sql, request.getId());
}

'id'가 존재하지 않을 때 응답 결과가 200이 나오지 않게 예외 처리를 했습니다.

Request

public class FruitRequest {

    private Long id;
    private String name;
    private LocalDate warehousingDate;
    private long price;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public LocalDate getWarehousingDate() {
        return warehousingDate;
    }

    public long getPrice() {
        return price;
    }
}

기존에 있던 FruitRequest에 id를 추가해서 사용했습니다.

결과

image해당 id가 존재하지 않으면 500 상태 코드를 받게 됩니다.

imageid가 데이터에 존재하면 200을 받을 수 있고,

image'stat'의 값이 바뀔 수 있는 것을 볼 수 있습니다.

 

 

문제 3

우리는 특정 과일을 기준으로 팔린 금액, 팔리지 않은 금액을 조회하고 싶습니다.
예를 들어,
1. (1, 사과, 3000원, 판매 O)
2. (2, 사과, 4000원, 판매 X)
3. (3, 사과, 3000원, 판매 O)
와 같은 세 데이터가 있다면 우리의 API는 판매된 금액 : 6000원, 판매되지 않은 금액 : 4000원 이라고 응답해야 합니다.

구체적인 스펙은 다음과 같습니다.

  • HTTP method : GET

  • HTTP path : /api/v1/fruit/stat

  • HTTP query

    • name : 과일 이름

  • HTTP 요청 Body

{
  "salesAmount": long,
  "notSalesAmount": long
}
  • HTTP 응답 Body 예시

{
  "salesAmount": 6000,
  "notSalesAmount": 4000
}

image테이블의 데이터를 입력했습니다.

Controller

@GetMapping("/api/v1/fruit/stat")
public FruitSaleResponse readSoldFruit(@RequestParam String name) {
    String readSql = "SELECT * FROM fruit WHERE name = ?";
    boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty();
    if (isFruitNotExist) {
        throw new IllegalArgumentException();
    }

    String sql1 = "SELECT * FROM fruit WHERE name = ? AND stat = true";
    List<Long> salesAmount = jdbcTemplate.query(sql1, (rs, rowNum) -> {
        long amount = rs.getLong("price");
        return amount;
    }, name);

    String sql2 = "SELECT * FROM fruit WHERE name = ? AND stat = false";
    List<Long> notSalesAmount = jdbcTemplate.query(sql2, (rs, rowNum) -> {
        long amount = rs.getLong("price");
        return amount;
    }, name);

    long salesAmountSum = 0;
    for (long a : salesAmount) {
        salesAmountSum += a;
    }

    long notSalesAmountSum = 0;
    for (long a : notSalesAmount) {
        notSalesAmountSum += a;
    }

    return new FruitSaleResponse(salesAmountSum, notSalesAmountSum);
}

판매가 완료된 상태인 과일들의 총 가격과 판매가 되지 않은 상태인 과일들의 총 가격을 따로 구해서 FruitResponse의 인자로 넣어 인스턴스를 생성하도록 했습니다.

Response

public class FruitSaleResponse {

    private long salesAmount;
    private long notSalesAmount;

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

    public long getSalesAmount() {
        return salesAmount;
    }

    public long getNotSalesAmount() {
        return notSalesAmount;
    }
}

결과

image없는 이름의 값을 보내면 500 에러 발생

image

 

한 걸음 더!

SQL의 sum, group by 키워드를 검색해 적용해 보세요!

Controller

@GetMapping("/api/v1/fruit/stat")
public FruitSaleResponse readSoldFruit(@RequestParam String name) {
    String readSql = "SELECT * FROM fruit WHERE name = ?";
    boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty();
    if (isFruitNotExist) {
        throw new IllegalArgumentException();
    }

    String sql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY stat";
    List<Long> list = jdbcTemplate.query(sql, (rs, rowNum) -> {
        long amount = rs.getLong("SUM(price)");
        return amount;
    }, name);
    return new FruitSaleResponse(list.get(0), list.get(1));
}

SQL문을 실행했을 때 결과가

image이렇게 나오기 때문에 list의 0번 인덱스와 1번 인덱스를 받아 인스턴스를 생성하도록 했습니다.

결과

image

댓글을 작성해보세요.

채널톡 아이콘