본문 바로가기

Education/인프런 워밍업 클럽(BE 0기)

인프런 워밍업 클럽 - BE 0기, 과제 #6

목차

     

    과제

    문제1. 4일차 과제에서 만들었던 Fruit API를 Controller - Service - Repository로 분리해보세요. 

    문제2. 코드가 분리되면 FruitRepository를 FruitMemoryRepository와 FruitMySqlRepository로 나누고 @Primary 어노테이션을 활용해 두 Repository를 바꿔가며 동작시킬 수 있도록 코드를 변경해보세요.

    (@Qualifier 어노테이션을 사용해도 좋습니다.)


    풀이

    package com.group.libraryapp.controller.study.controller;
    
    import com.group.libraryapp.dto.study.d2.FruitRequest;
    import com.group.libraryapp.dto.study.d2.FruitSearchResponse;
    import com.group.libraryapp.dto.study.d2.FruitSellRequest;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    public class FruitController {
    
        private final JdbcTemplate jdbcTemplate;
    
        public FruitController(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        @PostMapping("/api/v1/fruit")
        public void saveFruit(@RequestBody FruitRequest request) {
            String sql = "INSERT INTO study_fruit (name, warehousingDate, price) VALUES (?, ?, ?)";
            jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
        }
    
        @PutMapping("/api/v1/fruit")
        public void sellFruit(@RequestBody FruitSellRequest request) {
            String readSql = "SELECT * FROM study_fruit WHERE id = ?";
            boolean isIdNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
    
            if (isIdNotExist) {
                throw new IllegalArgumentException();
            }
            String sql = "UPDATE study_fruit SET sold = 1 WHERE id = ?";
            jdbcTemplate.update(sql, request.getId());
        }
    
        @GetMapping("/api/v1/fruit/stat")
        public FruitSearchResponse searchFruitStat(@RequestParam String name) {
            String readSql = "SELECT COUNT(*) FROM study_fruit WHERE name = ?";
            int count = jdbcTemplate.queryForObject(readSql, Integer.class, name);
    
            if (count <= 0) {
                throw new IllegalArgumentException();
            }
    
            String sql = "SELECT SUM(price) AS total_price, " +
                    "CASE WHEN sold = 1 THEN 'salesAmount' ELSE 'notSalesAmount' END AS sold_group " +
                    "FROM study_fruit WHERE name = ? GROUP BY sold";
    
            return jdbcTemplate.query(sql, (rs) -> {
                FruitSearchResponse fruitSearchResponse = new FruitSearchResponse();
    
                while (rs.next()) {
                    String sold_group = rs.getString("sold_group");
                    int total_price = rs.getInt("total_price");
    
                    if ("salesAmount".equals(sold_group)) {
                        fruitSearchResponse.setSalesAmount(total_price);
                    } else if ("notSalesAmount".equals(sold_group)) {
                        fruitSearchResponse.setNotSalesAmount(total_price);
                    }
                }
    
                return fruitSearchResponse;
            }, name);
        }
    }

    Controller에 모든 소스가 작성됐었던 기존 코드를 Controller, Service, Repository로 역할에 맞게 구분해 주겠다.

    Controller는 클라이언트와 서버 사이의 중간 역할을 수행한다. Http 요청을 받고, 요청에 대한 서비스 메서드를 호출하고 다시 그 결과를 Http 응답으로 반환한다.

    Service는 비즈니스 로직을 처리하는 역할을 한다. 컨트롤러나 다른 서비스에서 요청한 내용을 처리한다.

    Repository는 데이터베이스의 데이터를 읽고 쓰는 작업을 수행한다. 데이터베이스에 접근하여 데이터의 CRUD 작업을 수행한다.

     

    package com.group.libraryapp.controller.study.controller;
    
    import com.group.libraryapp.controller.study.service.FruitService;
    import com.group.libraryapp.dto.study.d2.FruitRequest;
    import com.group.libraryapp.dto.study.d2.FruitSearchResponse;
    import com.group.libraryapp.dto.study.d2.FruitSellRequest;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    public class FruitController {
    
        private final FruitService fruitService;
    
        public FruitController(FruitService fruitService) {
            this.fruitService = fruitService;
        }
    
        @PostMapping("/api/v1/fruit")
        public void saveFruit(@RequestBody FruitRequest request) {
            fruitService.saveFruit(request);
        }
    
        @PutMapping("/api/v1/fruit")
        public void sellFruit(@RequestBody FruitSellRequest request) {
            fruitService.sellFruit(request);
        }
    
        @GetMapping("/api/v1/fruit/stat")
        public FruitSearchResponse searchFruitStat(@RequestParam String name) {
            return fruitService.searchFruitStat(name);
        }
    }

     

    package com.group.libraryapp.controller.study.service;
    
    import com.group.libraryapp.controller.study.repository.FruitRepository;
    import com.group.libraryapp.dto.study.d2.FruitRequest;
    import com.group.libraryapp.dto.study.d2.FruitSearchResponse;
    import com.group.libraryapp.dto.study.d2.FruitSellRequest;
    import org.springframework.stereotype.Service;
    
    @Service
    public class FruitService {
    
        private final FruitRepository fruitRepository;
    
        public FruitService(FruitRepository fruitRepository) {
            this.fruitRepository = fruitRepository;
        }
    
        public void saveFruit(FruitRequest request) {
            fruitRepository.saveFruit(request.getName(), request.getWarehousingDate(), request.getPrice());
        }
    
    
    
        public void sellFruit(FruitSellRequest request) {
            if (fruitRepository.findById(request.getId())) {
                throw new IllegalArgumentException();
            }
    
            fruitRepository.sellFruit(request.getId());
        }
    
        public FruitSearchResponse searchFruitStat(String name) {
    
            int count = fruitRepository.findByName(name);
    
            if (count <= 0) {
                throw new IllegalArgumentException();
            }
    
            return fruitRepository.searchFruitStat(name);
        }
    
    }
    
    package com.group.libraryapp.controller.study.repository;
    
    import com.group.libraryapp.dto.study.d2.FruitSearchResponse;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import java.util.Date;
    
    @Repository
    public class FruitRepository {
    
        private final JdbcTemplate jdbcTemplate;
    
        public FruitRepository(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        public void saveFruit(String name, Date warehousingDate, long price) {
            String sql = "INSERT INTO study_fruit (name, warehousingDate, price) VALUES (?, ?, ?)";
            jdbcTemplate.update(sql, name, warehousingDate, price);
        }
    
        public boolean findById(long id) {
            String readSql = "SELECT * FROM study_fruit WHERE id = ?";
            return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
        }
    
        public void sellFruit(long id) {
            String sql = "UPDATE study_fruit SET sold = 1 WHERE id = ?";
            jdbcTemplate.update(sql, id);
        }
    
        public int findByName(String name) {
            String readSql = "SELECT COUNT(*) FROM study_fruit WHERE name = ?";
            return jdbcTemplate.queryForObject(readSql, Integer.class, name);
        }
    
        public FruitSearchResponse searchFruitStat(String name) {
    
            String sql = "SELECT SUM(price) AS total_price, " +
                    "CASE WHEN sold = 1 THEN 'salesAmount' ELSE 'notSalesAmount' END AS sold_group " +
                    "FROM study_fruit WHERE name = ? GROUP BY sold";
    
            return jdbcTemplate.query(sql, (rs) -> {
                FruitSearchResponse fruitSearchResponse = new FruitSearchResponse();
    
                while (rs.next()) {
                    String sold_group = rs.getString("sold_group");
                    int total_price = rs.getInt("total_price");
    
                    if ("salesAmount".equals(sold_group)) {
                        fruitSearchResponse.setSalesAmount(total_price);
                    } else if ("notSalesAmount".equals(sold_group)) {
                        fruitSearchResponse.setNotSalesAmount(total_price);
                    }
                }
                return fruitSearchResponse;
            }, name);
        }
    }
    

    각 클래스를 스프링 빈으로 등록해 주기 위해서 @RestController, @Service, @Repository 어노테이션을 사용했고, 생성자 주입 방식으로 스프링 빈을 주입받도록 했다.

    JdbcTemplate는 데이터를 조작하는 Repository에서 주입받는다.


    문제2처럼 FruitRepository를 Memory와 Mysql로 분리하려면 Interface가 필요하다.

    package com.group.libraryapp.controller.study.repository;
    
    import com.group.libraryapp.dto.study.d2.FruitSearchResponse;
    
    import java.util.Date;
    
    public interface FruitRepository {
    
        public void saveFruit(String name, Date warehousingDate, long price);
        public boolean findById(long id);
        public void sellFruit(long id);
        public int findByName(String name);
        public FruitSearchResponse searchFruitStat(String name);
    }

    그 후 FruitMemoryRepository와 FruitMySqlRepository에서 FruitRepository 인터페이스를 구현하도록 했다.

    package com.group.libraryapp.controller.study.repository;
    
    import com.group.libraryapp.dto.study.d2.FruitSearchResponse;
    import org.springframework.stereotype.Repository;
    
    import java.util.Date;
    
    @Repository
    public class FruitMemoryRepository implements FruitRepository{
        @Override
        public void saveFruit(String name, Date warehousingDate, long price) {
            
        }
    
        @Override
        public boolean findById(long id) {
            return false;
        }
    
        @Override
        public void sellFruit(long id) {
    
        }
    
        @Override
        public int findByName(String name) {
            return 0;
        }
    
        @Override
        public FruitSearchResponse searchFruitStat(String name) {
            return null;
        }
    }
    
    package com.group.libraryapp.controller.study.repository;
    
    import com.group.libraryapp.dto.study.d2.FruitSearchResponse;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import java.util.Date;
    
    @Repository
    public class FruitMySqlRepository implements FruitRepository{
        private final JdbcTemplate jdbcTemplate;
    
        public FruitMySqlRepository(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        public void saveFruit(String name, Date warehousingDate, long price) {
            String sql = "INSERT INTO study_fruit (name, warehousingDate, price) VALUES (?, ?, ?)";
            jdbcTemplate.update(sql, name, warehousingDate, price);
        }
    
        public boolean findById(long id) {
            String readSql = "SELECT * FROM study_fruit WHERE id = ?";
            return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
        }
    
        public void sellFruit(long id) {
            String sql = "UPDATE study_fruit SET sold = 1 WHERE id = ?";
            jdbcTemplate.update(sql, id);
        }
    
        public int findByName(String name) {
            String readSql = "SELECT COUNT(*) FROM study_fruit WHERE name = ?";
            return jdbcTemplate.queryForObject(readSql, Integer.class, name);
        }
    
        public FruitSearchResponse searchFruitStat(String name) {
    
            String sql = "SELECT SUM(price) AS total_price, " +
                    "CASE WHEN sold = 1 THEN 'salesAmount' ELSE 'notSalesAmount' END AS sold_group " +
                    "FROM study_fruit WHERE name = ? GROUP BY sold";
    
            return jdbcTemplate.query(sql, (rs) -> {
                FruitSearchResponse fruitSearchResponse = new FruitSearchResponse();
    
                while (rs.next()) {
                    String sold_group = rs.getString("sold_group");
                    int total_price = rs.getInt("total_price");
    
                    if ("salesAmount".equals(sold_group)) {
                        fruitSearchResponse.setSalesAmount(total_price);
                    } else if ("notSalesAmount".equals(sold_group)) {
                        fruitSearchResponse.setNotSalesAmount(total_price);
                    }
                }
                return fruitSearchResponse;
            }, name);
        }
    }
    

     

    이렇게 되면 FruitRepository를 구현하는 구현체가 2개이기 때문에 FruitRepository를 의존하는 FruitService에서 에러가 발생하게 된다.

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

     

    어느 스프링 빈을 사용할지 알려주는 방법은 2가지가 있다. @Primary 어노테이션을 이용해 우선권을 주는 방법과 @Qualify 어노테이션을 이용해 직접 명시하는 방법이다.

    @Qualify 어노테이션을 사용해서 MySqlRepository를 사용하도록 수정했다.

    private final FruitRepository fruitRepository;
    
    public FruitService(@Qualifier("fruitMySqlRepository") FruitRepository fruitRepository) {
        this.fruitRepository = fruitRepository;
    }

     

     

     

    오늘의 과제 끝!!!

    오늘 과제는 강의에서 배운 내용과 동일해서 어려움 없이 잘 진행했다.👍