❗학습목표
우리는 스프링 컨테이너의 개념을 배우고, 기존에 작성했던 Controller 코드를 3단 분리해보았습니다. 앞으로 API를 개발할 때는 이 계층에 맞게 각 코드가 작성되어야 합니다! 🙂
package com.group.fruitshopapp.controller;
import com.group.fruitshopapp.dto.FruitCreateRequest;
import com.group.fruitshopapp.dto.FruitGetStatResponse;
import com.group.fruitshopapp.dto.FruitUpdateRequest;
import com.group.fruitshopapp.service.FruitService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/fruit")
public class FruitController {
private final JdbcTemplate jdbcTemplate;
private final FruitService fruitService;
public FruitController(JdbcTemplate jdbcTemplate , FruitService fruitService) {
this.jdbcTemplate = jdbcTemplate;
this.fruitService = fruitService;
}
@PostMapping
public void createFruit(@RequestBody FruitCreateRequest request) {
fruitService.createFruit(request);
}
@PutMapping
public void updateFruit(@RequestBody FruitUpdateRequest request) {
fruitService.updateFruit(request);
}
@GetMapping("/stat")
public FruitGetStatResponse getStatOfFruit(@RequestParam String name) {
return fruitService.getStatOfFruit(name);
}
}
package com.group.fruitshopapp.service;
import com.group.fruitshopapp.dto.FruitCreateRequest;
import com.group.fruitshopapp.dto.FruitGetStatResponse;
import com.group.fruitshopapp.dto.FruitUpdateRequest;
import com.group.fruitshopapp.repository.FruitMySqlRepository;
import org.springframework.stereotype.Service;
@Service
public class FruitService {
private final FruitMySqlRepository fruitRepository;
public FruitService(FruitMySqlRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
public void createFruit(FruitCreateRequest request) {
fruitRepository.createFruit(request);
}
public void updateFruit(FruitUpdateRequest request) {
if (fruitRepository.isFruitNotExist(request)) {
throw new IllegalArgumentException();
}
fruitRepository.updateFruit(request);
}
public FruitGetStatResponse getStatOfFruit(String name) {
if (fruitRepository.isFruitNotExist(name)) {
throw new IllegalArgumentException();
}
return fruitRepository.getStatOfFruit(name);
}
}
package com.group.fruitshopapp.repository;
import com.group.fruitshopapp.dto.FruitCreateRequest;
import com.group.fruitshopapp.dto.FruitGetStatResponse;
import com.group.fruitshopapp.dto.FruitUpdateRequest;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.PutMapping;
import java.util.HashMap;
import java.util.Map;
@Primary
@Repository
public class FruitMySqlRepository{
private final JdbcTemplate jdbcTemplate;
public FruitMySqlRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void createFruit(FruitCreateRequest request) {
String sql = "INSERT INTO fruit(name, warehousingDate, price) VALUES (?,?,?)";
jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
}
@PutMapping
public void updateFruit(FruitUpdateRequest request) {
// 해당 id가 fruit 테이블 안에 존재하는지 검색하고 없다면 IllegalArgumentException 예외를 발생시킴
String sqlUpdate = "UPDATE fruit SET isSold = True WHERE id = ?";
jdbcTemplate.update(sqlUpdate, request.getId());
}
public FruitGetStatResponse getStatOfFruit(String name) {
String sql = "SELECT isSold, SUM(price) as SUM from fruit WHERE name = ? GROUP BY isSold";
Map<Boolean, Long> resultmap = new HashMap<>();
jdbcTemplate.query(sql, (rs, rowNum) -> {
boolean isSold = rs.getBoolean("isSold");
long price = rs.getLong("SUM");
resultmap.put(isSold, price);
return null;
}, name);
return new FruitGetStatResponse(resultmap.get(true), resultmap.get(false));
}
public boolean isFruitNotExist(FruitUpdateRequest request) {
String sqlRead = "SELECT * FROM fruit WHERE id = ?";
return jdbcTemplate.query(sqlRead, (rs, rowNum) -> 0, request.getId())
.isEmpty();
}
public boolean isFruitNotExist(String name) {
String sqlRead = "SELECT * FROM fruit WHERE name = ?";
return jdbcTemplate.query(sqlRead, (rs, rowNum) -> 0, name)
.isEmpty();
}
}
FruitRepository
인터페이스public interface FruitRepository {
public void createFruit(FruitCreateRequest request);
public void updateFruit(FruitUpdateRequest request);
public FruitGetStatResponse getStatOfFruit(String name);
public boolean isFruitNotExist(FruitUpdateRequest request);
public boolean isFruitNotExist(String name);
}
FruitMemoryRepository
클래스package com.group.fruitshopapp.repository;
import com.group.fruitshopapp.domain.Fruit;
import com.group.fruitshopapp.dto.FruitCreateRequest;
import com.group.fruitshopapp.dto.FruitGetStatResponse;
import com.group.fruitshopapp.dto.FruitUpdateRequest;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
//@Primary
@Repository
public class FruitMemoryRepository implements FruitRepository{
private final List<Fruit> fruits = new ArrayList<>();
private Long nextId = 1L;
public FruitMemoryRepository() {
}
@Override
public void createFruit(FruitCreateRequest request) {
Fruit fruit = new Fruit();
fruit.setId(nextId++);
fruit.setName(request.getName());
fruit.setWarehousingDate(request.getWarehousingDate());
fruit.setPrice(request.getPrice());
fruits.add(fruit);
}
@Override
public void updateFruit(FruitUpdateRequest request) {
Fruit fruit = findFruitById(request.getId());
if (fruit != null) {
fruit.setSold(true);
} else {
throw new IllegalArgumentException();
}
}
@Override
public FruitGetStatResponse getStatOfFruit(String name) {
long sumSold = 0;
long sumNotSold = 0;
for (Fruit fruit : fruits) {
if (fruit.getName().equals(name)) {
if (fruit.isSold()) {
sumSold += fruit.getPrice();
} else {
sumNotSold += fruit.getPrice();
}
}
}
return new FruitGetStatResponse(sumSold, sumNotSold);
}
@Override
public boolean isFruitNotExist(FruitUpdateRequest request) {
Fruit fruit = findFruitById(request.getId());
return fruit == null;
}
@Override
public boolean isFruitNotExist(String name) {
for (Fruit fruit : fruits) {
if (fruit.getName().equals(name)) {
return false;
}
}
return true;
}
private Fruit findFruitById(Long id) {
for (Fruit fruit : fruits) {
if (fruit.getId() == id) {
return fruit;
}
}
return null;
}
}
FruitMySqlRepositroy
클래스package com.group.fruitshopapp.repository;
import com.group.fruitshopapp.dto.FruitCreateRequest;
import com.group.fruitshopapp.dto.FruitGetStatResponse;
import com.group.fruitshopapp.dto.FruitUpdateRequest;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;
@Primary
@Repository
public class FruitMySqlRepository implements FruitRepository{
private final JdbcTemplate jdbcTemplate;
public FruitMySqlRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void createFruit(FruitCreateRequest request) {
String sql = "INSERT INTO fruit(name, warehousingDate, price) VALUES (?,?,?)";
jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
}
@Override
public void updateFruit(FruitUpdateRequest request) {
// 해당 id가 fruit 테이블 안에 존재하는지 검색하고 없다면 IllegalArgumentException 예외를 발생시킴
String sqlUpdate = "UPDATE fruit SET isSold = True WHERE id = ?";
jdbcTemplate.update(sqlUpdate, request.getId());
}
@Override
public FruitGetStatResponse getStatOfFruit(String name) {
String sql = "SELECT isSold, SUM(price) as SUM from fruit WHERE name = ? GROUP BY isSold";
Map<Boolean, Long> resultmap = new HashMap<>();
jdbcTemplate.query(sql, (rs, rowNum) -> {
boolean isSold = rs.getBoolean("isSold");
long price = rs.getLong("SUM");
resultmap.put(isSold, price);
return null;
}, name);
return new FruitGetStatResponse(resultmap.get(true), resultmap.get(false));
}
@Override
public boolean isFruitNotExist(FruitUpdateRequest request) {
String sqlRead = "SELECT * FROM fruit WHERE id = ?";
return jdbcTemplate.query(sqlRead, (rs, rowNum) -> 0, request.getId())
.isEmpty();
}
@Override
public boolean isFruitNotExist(String name) {
String sqlRead = "SELECT * FROM fruit WHERE name = ?";
return jdbcTemplate.query(sqlRead, (rs, rowNum) -> 0, name)
.isEmpty();
}
}
package com.group.fruitshopapp.service;
import com.group.fruitshopapp.dto.FruitCreateRequest;
import com.group.fruitshopapp.dto.FruitGetStatResponse;
import com.group.fruitshopapp.dto.FruitUpdateRequest;
import com.group.fruitshopapp.repository.FruitMySqlRepository;
import com.group.fruitshopapp.repository.FruitRepository;
import org.springframework.stereotype.Service;
@Service
public class FruitService {
private final FruitRepository fruitRepository;
public FruitService(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
public void createFruit(FruitCreateRequest request) {
fruitRepository.createFruit(request);
}
public void updateFruit(FruitUpdateRequest request) {
if (fruitRepository.isFruitNotExist(request)) {
throw new IllegalArgumentException();
}
fruitRepository.updateFruit(request);`
}
public FruitGetStatResponse getStatOfFruit(String name) {
if (fruitRepository.isFruitNotExist(name)) {
throw new IllegalArgumentException();
}
return fruitRepository.getStatOfFruit(name);
}
}
@JsonCreator와 @Getter를 적용함
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.Getter;
@Getter
public class FruitUpdateRequest {
private final Long id;
@JsonCreator
public FruitUpdateRequest(Long id) {
this.id = id;
}
}
@Profile 어노테이션이 뭔지 알아보기
@Qualifier, @Primary, @Profile 어노테이션을 직접 써보고 장단점 파악해보기
개발을 공부 할 때 기술의 장단점을 직접 써 보면서 확인해봐야 뭐가 잘못되고 뭐가 나쁜건지 알게되는 것 같다.
그럼에도 불구하고 각 공부를 하기전에 간단하게 라도 배우려는 기술에 대해 구글링등 리서치를 통해 확인한 다음에 그 기술에 대해 공부를 하거나 프로젝트를 진행하는 것이 좋다.
그래야 프로젝트에 적절치 못한 기술을 사용했을 떄 생기는 사고를 줄일 수 있다.
최근에 읽고 있는 책인파이썬알고리즘인터뷰
책에서도
코테 풀이 방법에 대해 먼저 설명하지 않고 파이썬의 장단점에 대해 먼저 설명을 한다. 이 책의 첫 장에서 파이썬이 코딩테스트용 언어로 적절한지 파이썬 외의 다른 언어들(cpp, java, typscript)과 생산성, 적절성, 문법, 속도, 가독성면에서 비교한다. 이를 통해 파이썬이 코테용 언어로 써도 적절하다는 것을 설명하고 있다.
이 책의 저자처럼 먼저 해당 언어가 코테에 적절한지 먼저 확인을 함으로써 코테에 쓰기 힘든 언어로 공부했다가 다시 언어를 바꿔야 되는 참사를 줄일 수 있을 것이다.
아래 강의 링크의 내용을 바탕으로 글이 작성됨
자바-스프링부트-서버개발-올인원-인프런
그외 참고 링크
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor