인프런 워밍업 클럽 백엔드 - 여섯 번째 과제
문제 1
과제 #4에서 만들었던 API를 분리해보며, Controller - Service - Repository 계층에 익숙해져 봅시다!
문제 2
문제 1에서 코드가 분리되면 FruitController
/ FruitService
/ FruitRepository
가 생겼을 것입니다.
기존에 작성했던 FruitRepository
를 FruitMemoryRepository
와 FruitMysqlRepository
로 나누고@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 }
세 데이터를 보냈을 때,
메모리에 잘 저장된 것을 볼 수 있습니다.
HTTP method :
PUT
HTTP path :
/api/v1/fruit
{ "id": 1 }
{ "id": 3 }
두 데이터를 보냈을 때,
id가 1, 3인 stat이 true로 잘 바뀐 것을 볼 수 있습니다.
HTTP method :
GET
HTTP path :
/api/vi/fruit/stat?name=사과
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 }
결과
HTTP method :
PUT
HTTP path :
/api/v1/fruit
{ "id": 1 }
{ "id": 3 }
결과
HTTP method :
GET
HTTP path :
/api/vi/fruit/stat?name=사과
댓글을 작성해보세요.