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

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

문제 1

과제 #4에서 만들었던 API를 분리해보며, Controller - Service - Repository 계층에 익숙해져 봅시다!

 

문제 2

문제 1에서 코드가 분리되면 FruitController / FruitService / FruitRepository가 생겼을 것입니다.

기존에 작성했던 FruitRepositoryFruitMemoryRepositoryFruitMysqlRepository로 나누고
@Primary 어노테이션을 활용해 두 Repository를 바꿔가며 동작시킬 수 있도록 코드를 변경해보세요!

@Qualifier 어노테이션을 사용해도 좋습니다!

 

기존의 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());
    }

    @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());
    }

    @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));
    }
}

 

수정된 Conroller

@RestController
public class FruitController {

    private final FruitService fruitService;

    public FruitController(FruitService fruitService) {
        this.fruitService = fruitService;
    }

    @PostMapping("/api/v1/fruit")
    public void warehousing(@RequestBody FruitRequest request) {
        fruitService.saveFruit(request);
    }

    @PutMapping("/api/v1/fruit")
    public void sold(@RequestBody FruitRequest request) {
        fruitService.soldFruit(request);
    }

    @GetMapping("/api/v1/fruit/stat")
    public FruitSaleResponse readSoldFruit(@RequestParam String name) {
        return fruitService.readFruitSoldPrice(name);
    }
}

 

Service

@Service
public class FruitService {

    private final FruitRepository fruitRepository;

    public FruitService(@Qualifier("사용할 리포지토리") FruitRepository fruitRepository) {
        this.fruitRepository = fruitRepository;
    }

    public void saveFruit(FruitRequest request) {
        fruitRepository.saveFruit(request.getName(), request.getWarehousingDate(), request.getPrice());
    }

    public void soldFruit(FruitRequest request) {
        if (fruitRepository.isFruitNotExistById(request.getId())) {
            throw new IllegalArgumentException();
        }
        fruitRepository.soldFruit(request.getId());
    }

    public FruitSaleResponse readFruitSoldPrice(String name) {
        if (fruitRepository.isFruitNotExistByName(name)) {
            throw new IllegalArgumentException();
        }
        return fruitRepository.readFruitSoldPrice(name);
    }
}

@Qualifier 어노테이션안에 사용할 리포지토리를 작성하면 된다.
'fruitMemoryRepository'를 작성하면 FruitMemoryRepository를 사용하고,
'fruitMysqlRepository'를 작성하면 FruitMysqlRepository를 사용할 수 있다.

 

Repository

package com.example.task.repository.fruit;

import com.example.task.dto.fruit.response.FruitSaleResponse;

import java.time.LocalDate;
import java.util.Date;

public interface FruitRepository {
    // 과일 저장
    void saveFruit(String name, LocalDate warehousingDate, long price);
    // 과일 판매
    void soldFruit(long id);
    // 해당 이름의 과일 판매 금액, 판매되지 않은 금액
    FruitSaleResponse readFruitSoldPrice(String name);
    // 해당 ID의 과일이 존재하는지 (없으면 true)
    boolean isFruitNotExistById(long id);
    // 해당 이름의 과일이 존재하는지 (없으면 true)
    boolean isFruitNotExistByName(String name);
}

 

MemoryRepository

@Repository
public class FruitMemoryRepository implements FruitRepository {

    private List<FruitResponse> fruitResponses = new ArrayList<>();

    @Override
    public void saveFruit(String name, LocalDate warehousingDate, long price) {
        fruitResponses.add(new FruitResponse(fruitResponses.size() + 1, name, warehousingDate, price, false));
        print();
    }

    @Override
    public void soldFruit(long id) {
        for (FruitResponse fruitResponse : fruitResponses) {
            if (fruitResponse.getId() == id) {
                fruitResponse.setStat(true);
                return;
            }
        }
        print();
    }

    @Override
    public FruitSaleResponse readFruitSoldPrice(String name) {
        long salesAmount = 0;
        long notSalesAmount = 0;

        for (FruitResponse fruitResponse : fruitResponses) {
            if (fruitResponse.isStat()) {
                salesAmount += fruitResponse.getPrice();
            } else {
                notSalesAmount += fruitResponse.getPrice();
            }
        }
        print();
        return new FruitSaleResponse(salesAmount, notSalesAmount);
    }

    @Override
    public boolean isFruitNotExistById(long id) {
        for (FruitResponse fruitResponse : fruitResponses) {
            if (fruitResponse.getId() == id) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean isFruitNotExistByName(String name) {
        for (FruitResponse fruitResponse : fruitResponses) {
            if (fruitResponse.getName().equals(name)) {
                return false;
            }
        }
        return true;
    }

    // 잘 실행되었는지 확인하기 위해 출력
    private void print() {
        for (FruitResponse response : fruitResponses) {
            System.out.println(response);
        }
    }
}

 

Memory에 저장할 FruitResponse

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

import java.time.LocalDate;

public class FruitResponse {

    private long id;
    private String name;
    private LocalDate warehousingDate;
    private long price;
    private boolean stat;

    public FruitResponse(long id, String name, LocalDate warehousingDate, long price, boolean stat) {
        this.id = id;
        this.name = name;
        this.warehousingDate = warehousingDate;
        this.price = price;
        this.stat = stat;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDate getWarehousingDate() {
        return warehousingDate;
    }

    public void setWarehousingDate(LocalDate warehousingDate) {
        this.warehousingDate = warehousingDate;
    }

    public long getPrice() {
        return price;
    }

    public void setPrice(long price) {
        this.price = price;
    }

    public boolean isStat() {
        return stat;
    }

    public void setStat(boolean stat) {
        this.stat = stat;
    }

    @Override
    public String toString() {
        return "FruitResponse{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", warehousingDate=" + warehousingDate +
                ", price=" + price +
                ", stat=" + stat +
                '}';
    }
}

MemoryRepository 사용

  • HTTP method : POST

  • HTTP path : /api/vi/fruit

{ "name": "사과", "warehousingDate": "2024-01-01", "price": 3000 }
{ "name": "사과", "warehousingDate": "2024-01-01", "price": 1000 }
{ "name": "사과", "warehousingDate": "2024-01-01", "price": 3000 }

세 데이터를 보냈을 때,

image메모리에 잘 저장된 것을 볼 수 있습니다.

 

  • HTTP method : PUT

  • HTTP path : /api/v1/fruit

{ "id": 1 }
{ "id": 3 }

두 데이터를 보냈을 때,

imageid가 1, 3인 stat이 true로 잘 바뀐 것을 볼 수 있습니다.

 

  • HTTP method : GET

  • HTTP path : /api/vi/fruit/stat?name=사과

image

 

MysqlRepository

@Repository
public class FruitMysqlRepository implements FruitRepository {

    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public void saveFruit(String name, LocalDate warehousingDate, long price) {
        String sql = "INSERT INTO fruit (name, warehousingDate, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, name, warehousingDate, price);
    }

    @Override
    public void soldFruit(long id) {
        String sql = "UPDATE fruit SET stat = true WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }

    @Override
    public FruitSaleResponse readFruitSoldPrice(String name) {
        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));
    }

    @Override
    public boolean isFruitNotExistById(long id) {
        String readSql = "SELECT * FROM fruit WHERE id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }

    @Override
    public boolean isFruitNotExistByName(String name) {
        String readSql = "SELECT * FROM fruit WHERE name = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty();
    }
}

MysqlRepository 사용

  • HTTP method : POST

  • HTTP path : /api/vi/fruit

{ "name": "사과", "warehousingDate": "2024-01-01", "price": 3000 }
{ "name": "사과", "warehousingDate": "2024-01-01", "price": 5000 }
{ "name": "사과", "warehousingDate": "2024-01-01", "price": 4000 }

결과

image

  • HTTP method : PUT

  • HTTP path : /api/v1/fruit

{ "id": 1 }
{ "id": 3 }

결과

image

  • HTTP method : GET

  • HTTP path : /api/vi/fruit/stat?name=사과

image

댓글을 작성해보세요.