📚 인프런 강의와 스터디를 바탕으로 한 과제를 풀이한 포스트입니다.
과일 가게 운영을 표현하는 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> 과일 정보 저장하기
@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;
}
}
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());
}
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 쿼리에 사용했다.<실행 결과>
<한 걸음 더>
<문제 2> 과일이 판매되었다면 팔렸다고 기록하기
purchase
의 default 값을 false(0)으로 지정해두었는데, id를 입력받아 해당하는 행의 purchase
값을 true(1)로 UPDATE
해준다.
@PutMapping("/api/v1/fruit") // PUT //api/v1/fruit
public void updateFruit (@RequestBody FruitUpdateRequest request)
{
fruitService.updateFruit(request);
}
RequestBody
를 넘겨준다.** FruitUpdateRequest
public class FruitUpdateRequest {
private long id;
public long getId() {
return id;
}
}
// U
public void updateFruit(FruitUpdateRequest request) {
fruitRepository.updateFruit(request.getId());
}
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);
}
jdbcTemplate.update
를 UPDATE 쿼리에 사용했다.<실행 결과>
purchase
가 1로 update된 것을 알 수 있다.<문제 3> 특정 과일을 기준으로 팔린 금액, 팔리지 않은 금액 조회하기
@GetMapping("/api/v1/fruit/stat")
public FruitResponse getFruit(@RequestParam String name) {
return fruitService.getFruit(name);
}
salesAmount
와 notSaleAmount
변수 정보를 가질 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;
}
}
// R
public FruitResponse getFruit(String name) {
return fruitRepository.getFruit(name);
}
@RequestParam
으로 받는 name을 넘겨주게 된다. 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);
}
purchase
의 값에 따라 더해주었다. (*처음에 sql로 시도하다가 계속 풀리지 않아서 구글링을 통해 참고했다.)purchase
와 price
이기 때문에 이 정보만 가지고 있을 클래스 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;
}
}
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에 해당하는 행이 각각 출력된 것이 아니어서 purchase
와 total_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;
}
}
isPurchase()
가 1이면(즉 팔린 과일이라면) salesAmount
에 더하고, 그렇지 않으면 notSalesAmount
에 더한다. 그 후 FruitResponse
의 생성자를 return하면 된다.<실행 결과>
Postman으로 확인해 보면 잘 작동하고 있는 것을 볼 수 있다.
정리해 보면, HTTP 응답 바디를 받을 때는 필요한 변수들을 가지고 있는 클래스들을 새로 정의해서(이 문제에서는 Fruit
클래스나 FruitGroupby
클래스가 해당) 여기에 sql의 실행 결과로 나온 row 중 필요한 column들만 assign 해줘야 한다.
그리고 for문을 돌면서 각 row들을 검사하고, 조건에 맞다면 더해준다. 최종적으로는 FruitResponse
라는 salesAmount
와 notSalesAmount
를 가진 클래스의 생성자를 return해서 문제에서 제시된 것과 같은 결과를 얻을 수 있다.
또한 구매 여부를 표시하는 purchase
의 자료형을 처음에는varchar
로 했었는데, 판매 여부를 판단할 때는 boolean
을 사용하는 것이 훨씬 편하고, 적합한 자료형이다.
[Reference]