6일차 스프링 컨테이너의 개념

nakyeonko3·2024년 2월 26일
0
post-thumbnail

❗학습목표
우리는 스프링 컨테이너의 개념을 배우고, 기존에 작성했던 Controller 코드를 3단 분리해보았습니다. 앞으로 API를 개발할 때는 이 계층에 맞게 각 코드가 작성되어야 합니다! 🙂

전체 폴더 구조

전체 소스코드 링크

library-app-/src/main/java/com/group/fruitshopapp at 26a69fed96872891d4f996b6d2a441ac6cfd84d7 · nakyeonko3/library-app- · GitHub

문제1


  • Controller
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);
    }
}
  • Service
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);
    }
}
  • Repository
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();
    }

}

문제2


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

}

메모

FruitUpdateRequest수정

@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)과 생산성, 적절성, 문법, 속도, 가독성면에서 비교한다. 이를 통해 파이썬이 코테용 언어로 써도 적절하다는 것을 설명하고 있다.

이 책의 저자처럼 먼저 해당 언어가 코테에 적절한지 먼저 확인을 함으로써 코테에 쓰기 힘든 언어로 공부했다가 다시 언어를 바꿔야 되는 참사를 줄일 수 있을 것이다.

참고


profile
웹개발자를 지망하고 있는 대학생, 진순파

0개의 댓글