[인프런 워밍업 클럽_0기] BE 여섯 번째 과제 (진도표 6일차)

2024. 2. 26. 02:38·공부/인프런 워밍업 클럽_BE
반응형

배너를 클릭하면 해당 사이트로 이동

 

강의 출처

 

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인

Java와 Spring Boot, JPA, MySQL, AWS를 이용해 서버를 개발하고 배포합니다. 웹 애플리케이션을 개발하며 서버 개발에 필요한 배경지식과 이론, 다양한 기술들을 모두 학습할 뿐 아니라, 다양한 옵션들

www.inflearn.com

 

진도표 6일차와 연결됩니다
우리는 스프링 컨테이너의 개념을 배우고, 기존에 작성했던 Controller 코드를 3단 분리해보았습니다. 앞으로 API를 개발할 때는 이 계층에 맞게 각 코드가 작성되어야 합니다! 🙂
과제 #4 에서 만들었던 API를 분리해보며, Controller - Service - Repository 계층에 익숙해져 봅시다! 👍

 

문제 1

 

FruitController 대폭 수정

일단 기존에 적혀있던 JdbcTemplate 관련 코드를 삭제했다! FruiteRepository를 @Repository로 스프링 빈에 등록해 줄 것이라 Controller에는 필요 없어서 제거했다. 그리고 생성자로 FruitService를 주입받게 하고 Controller 코드를 정리했다.

@RequestMapping("/api/v1")
@RestController
public class FruitController {

    private final FruitService fruitService;


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

    @PostMapping("/fruit")
    public void save(@RequestBody FruitSaveRequest request) {
        fruitService.save(request);
    }

    @PutMapping("/fruit")
    public void update(@RequestBody long id) {
       fruitService.update(id);
    }

    @GetMapping("/fruit/stat")
    public FruitStatResponse fruitStat (@RequestParam String name) {
        return fruitService.fruitStat(name);
    }
}

 

FruitService 생성

FruitService는 스프링 빈에 등록하기 위해서 @Service 어노테이션을 사용했다.

그리고 필드 및 생성자로 FruitMysqlRepository 구현체를 주입받았다. 이 부분은 문제 2에서 변경될 예정이다.

@Service
public class FruitService {

    private final FruitMysqlRepository fruitMysqlRepository;

    public FruitService(FruitMysqlRepository fruitMysqlRepository) {
        this.fruitMysqlRepository = fruitMysqlRepository;
    }

    public void save(FruitSaveRequest request) {
        fruitMysqlRepository.save(request);
    }

    public void update(long name) {
        fruitMysqlRepository.update(name);
    }

    public FruitStatResponse fruitStat(String name) {

        long salesAmount = fruitMysqlRepository.getAmount(name, 1);
        long notSalesAmount = fruitMysqlRepository.getAmount(name, 0);

        return new FruitStatResponse(salesAmount, notSalesAmount);
    }
}

FruitService를 작성하면서 fruitStat() 메서드를 리팩토링 할 수 있을 것 같아 냉큼 변경했다.

기존에 방식은 아래와 같다.

@GetMapping("/fruit/stat")
public FruitStatResponse fruitStat (@RequestParam String name) {

    String salesSql = "select sum(price) as sum from fruit where salesYN = 1 group by name having name = ?";
    String notSalesSql = "select sum(price) as sum from fruit where salesYN = 0 group by name having name = ?";

    long salesAmount = getSum(salesSql, name);
    long notSalesAmount = getSum(notSalesSql, name);

    return new FruitStatResponse(salesAmount, notSalesAmount);
}


private long getSum(String sql, String fruitNm) {
    return jdbcTemplate.queryForObject(sql, Long.class, fruitNm);
}

sql에 salesYN 값을 하드코딩 해 놓아서 sql문을 두 개나 만들었는데 salesYN 부분을 동적으로 줘서 하나의 sql로 변경해주려고 repository의 getAmount() 메서드의 salesYN 값을 매개변수로 넘겨주었다.

 

반응형

 

FruitRepository 생성 (FruitMysqlRepository)

FruitMysqlRepository에 @Repository 어노테이션을 붙여 스프링 빈으로 등록했다. 덕분에 Controller에서부터 생성자 매개변수로 받아오던 JdbcTemplate을 Repository에서 직접 불러올 수 있게 되었다!

@Repository
public class FruitMysqlRepository  {

    private final JdbcTemplate jdbcTemplate;

    public FruitMysqlRepository(JdbcTemplate jdbcTemplate) {
        System.out.println("FruitMysqlRepository.FruitMysqlRepository");
        this.jdbcTemplate = jdbcTemplate;
    }

    public void save(FruitSaveRequest request){
        String sql = "insert into fruit (name, warehousingDate, price) values (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
    }

    public void update(long id) {
        String sql =  "update fruit set salesYN = 1 where id = ?";
        jdbcTemplate.update(sql, id);
    }

    public long getAmount(String name, int salesYN) {
        String salesSql = "select sum(price) as sum from fruit where salesYN = ? group by name having name = ?";
        return jdbcTemplate.queryForObject(salesSql, Long.class, salesYN, name);
    }
}

 

반응형

 

문제 2

 

FruitRepository Interface 생성

두 개의 구현체가 상속받을 FruitRepository Interface를 생성한다.

public interface FruitRepository {
    void save(FruitSaveRequest request);
    void update(long id);
    long getAmount(String name, int salesYN);
}

 

FruitMysqlRepository 수정

@Repository
public class FruitMysqlRepository implements FruitRepository{ ... }

 

FruitMemoryRepository 생성

@Primary
@Repository
public class FruitMemoryRepository implements FruitRepository{

    private final List<Fruit> fruits = new ArrayList<>();
    private long sequence = 0L;

    @Override
    public void save(FruitSaveRequest request) {
        System.out.println("FruitMemoryRepository.save");
        fruits.add(new Fruit(++sequence, request.getName(),
                    request.getPrice(), request.getWarehousingDate()));
    }

    @Override
    public void update(long id) {
        System.out.println("FruitMemoryRepository.update");
        for (Fruit fruit : fruits) {
            if(fruit.getId() == id) {
                fruit.setSalesYN();
                break;
            }
        }
    }

    @Override
    public long getAmount(String name, int salesYN) {
        System.out.println("FruitMemoryRepository.getAmount");
        long amount = 0;

        for (Fruit fruit : fruits) {
            if(fruit.getName().equals(name))
            {
                if(fruit.getSalesYN() == salesYN) {
                    amount += fruit.getPrice();
                }
            }
        }
        return amount;
    }
}

FruitMemoryRepository의 save() 메서드를 구현하면서 Fruit의 domain이 뭔가 수상하다는 걸 알았다..! 필드를 제대로 적어주지 않았고, 생성자 또한 조금 대충 만들었던 듯하다.

반응형

 

 

기존  Fruit Doamin

public class Fruit {

    private long id;
    private String name;
    private long price;
    private int salesYN;

    public Fruit(long id, String name, long price, int salesYN) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.salesYN = salesYN;
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public long getPrice() {
        return price;
    }

    public int getSalesYN() {
        return salesYN;
    }
}

 

수정된 코드..

save()에서 매개변수로 받아오는 FruitSaveRequest의 필드가 name, warehousingDate, price 세 개인데 Fruit domain에 warehousingDate가 빠져있고, save() 개발시 필요한 id, name, price, warehousingDate 세 개의 매개변수를 가진 생성자도 만들어줬다. 그리고 update()를 위해 salesYN에 대한 setter를 만들어주었다.

public class Fruit {

    private long id;
    private String name;
    private long price;
    private LocalDate warehousingDate; // 추가
    private int salesYN;

    public Fruit(long id, String name, long price, LocalDate warehousingDate) { // 추가
        this.id = id;
        this.name = name;
        this.price = price;
        this.warehousingDate = warehousingDate;
    }

    public Fruit(long id, String name, long price, LocalDate warehousingDate, int salesYN) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.warehousingDate = warehousingDate;
        this.salesYN = salesYN;
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public long getPrice() {
        return price;
    }

    public LocalDate getWarehousingDate() {
        return warehousingDate;
    }

    public int getSalesYN() {
        return salesYN;
    }
    
    public void setSalesYN() {
        this.salesYN = 1;
    }
}

 

우선순위 정하기

  • FruitService 생성자 코드를 변경해준다. 그리고 하단의 변수명도 바꿔준다.
@Service
public class FruitService {

    private final FruitRepository fruitRepository;

    public FruitService(FruitRepository fruitRepository) {
        this.fruitRepository = fruitRepository;
    }

    public void save(FruitSaveRequest request) {
        fruitRepository.save(request);
    }

    public void update(long name) {
        fruitRepository.update(name);
    }

    public FruitStatResponse fruitStat(String name) {

        long salesAmount = fruitRepository.getAmount(name, 1);
        long notSalesAmount = fruitRepository.getAmount(name, 0);

        return new FruitStatResponse(salesAmount, notSalesAmount);
    }
}

 

반응형

 

@Primary

FruitMemoryRepository에 @Primary를 추가해준다.

  • save() 테스트

 

  • update() 테스트하려다가 에러 발생..!
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `long` from Object value (token `JsonToken.START_OBJECT`)]

#4번째 과제를 했었을 땐 잘 돌았었던 거 같은데 FruitMemoryRepository에서는 왜 오류가 날까..? 일단 dto를 만들어 에러를 피해보자.

  • FruitSalesUpdateRequest
public class FruitSalesUpdateRequest {
    private long id;

    public FruitSalesUpdateRequest() {
    }

    public FruitSalesUpdateRequest(long id) {
        this.id = id;
    }

    public long getId() {
        return id;
    }
}

 

  • FruitController 수정
@PutMapping("/fruit")
public void update(@RequestBody FruitSalesUpdateRequest request) {
   fruitService.update(request.getId());
}

 

다시 update() 포스트맨 테스트

성공..!!

 

반응형

 

  • 3번째 테스트를 위한 데이터 추가

id가 3인 데이터 판매 여부 업데이트

 

  • fruitStat() 테스트

성공!

 

반응형

 

@Qualifier

  • FruitMysqlRepository에 Qualifier 추가
@Qualifier("first")
@Repository
public class FruitMysqlRepository implements FruitRepository{ ... }

 

  • FruitService 수정
public FruitService(@Qualifier("first") FruitRepository fruitRepository) {
    this.fruitRepository = fruitRepository;
}

 

  • save() 포스트맨 테스트

서버를 실행하면 FruitMysqlRepository 생성자를 통해 콘솔에 println이 찍힌 걸 확인할 수 있다.

DB 확인

 

  • update() 포스트맨 확인

DB 확인

 

  • fruitStat() 포스트맨 확인

 

이번 과제는 강의에서 많이 다룬 내용이 대부분이라 많이 어렵진 않았다..! 다만 시간이 부족할 뿐..!

내일 출근 누가 해...

 

반응형
저작자표시 비영리 변경금지 (새창열림)
'공부/인프런 워밍업 클럽_BE' 카테고리의 다른 글
  • [인프런 워밍업 클럽_0기] BE 미니 프로젝트 가이드 (진도표 8일차 시작)
  • [인프런 워밍업 클럽_0기] BE 일곱 번째 과제 (진도표 7일차)
  • [인프런 워밍업 클럽_0기] BE 다섯 번째 과제 (진도표 5일차)
  • [인프런 워밍업 클럽_0기] BE 네 번째 과제 (진도표 4일차)
데부한
데부한
어차피 할 거면 긍정적으로 하고 싶은 개발자
    반응형
  • 데부한
    동동이개발바닥
    데부한
  • 전체
    오늘
    어제
    • 분류 전체보기 (307)
      • 방통대 컴퓨터과학과 (27)
        • 잡담 (9)
        • 3학년1학기 (17)
      • 프로젝트 및 컨퍼런스 회고 (1)
        • 프로젝트 (4)
        • 한이음 프로젝트 (0)
        • 회고 (3)
      • 공부 (165)
        • Spring (37)
        • JPA (71)
        • 인프런 워밍업 클럽_BE (10)
        • Java (6)
        • React.js (27)
        • 넥사크로 (11)
        • 기타 (3)
      • 알고리즘 (85)
        • 알고리즘 유형 (10)
        • 알고리즘 풀이 (57)
        • SQL 풀이 (18)
      • 에러 해결 (13)
      • 잡담 (7)
        • 국비교육 (2)
        • 구매후기 (5)
        • 진짜 잡담 (0)
  • 블로그 메뉴

    • Github
    • Linkedin
    • 홈
    • 방명록
    • 글쓰기
    • 관리
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    egov
    토비의스프링부트
    MSA
    백준
    프론트엔드
    에러해결
    넥사크로
    QueryDSL
    Spring
    코딩테스트
    인프런
    IT
    springboot
    프로그래머스
    전자정부프레임워크
    RESTful
    SpringBoot를 이용한 RESTful Web Service 개발
    자바스크립트
    JPA
    방통대
    react
    스프링부트
    운영체제
    기출문제
    Java
    oracle
    알고리즘
    SQL
    토이프로젝트
    개발자
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
데부한
[인프런 워밍업 클럽_0기] BE 여섯 번째 과제 (진도표 6일차)
상단으로

티스토리툴바