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

과제 #4 에서 만들었던 API를 분리해보며, Controller - Service - Repository 계층에 익숙해져 봅시다! 👍

문제 1

과제 #4에서 만들었던 API를 강의 내용처럼 Controller - Service - Repository로 분리해보세요!

코드 구현

controller > fruit > FruitController.java

package com.group.libraryapp.controller.fruit;

import com.group.libraryapp.dto.fruit.request.FruitRequest;
import com.group.libraryapp.dto.fruit.request.SoldFruitRequest;
import com.group.libraryapp.dto.fruit.response.FruitResponse;
import com.group.libraryapp.service.fruit.FruitService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class FruitController {
    private final FruitService fruitService;

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

    @PostMapping("/api/v1/fruit")
    public void saveFruitInfo(@RequestBody FruitRequest request) {
        fruitService.saveFruitInfo(request);
    }

    @PutMapping("/api/v1/fruit")
    public void saveSoldFruit(@RequestBody SoldFruitRequest request) {
        fruitService.saveSoldFruit(request);
    }

    @GetMapping("/api/v1/fruit/stat")
    public List<FruitResponse> showSalesAmount(@RequestParam String name) {
        return fruitService.showSalesAmount(name);
    }
}

service > fruit > FruitService.java

package com.group.libraryapp.service.fruit;

import com.group.libraryapp.dto.fruit.request.FruitRequest;
import com.group.libraryapp.dto.fruit.request.SoldFruitRequest;
import com.group.libraryapp.dto.fruit.response.FruitResponse;
import com.group.libraryapp.repository.fruit.FruitRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class FruitService {
    private final FruitRepository fruitRepository;

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

    public void saveFruitInfo(FruitRequest request) {
        fruitRepository.saveFruitInfo(request.getName(), request.getWarehosingDate(), request.getPrice());
    }

    public void saveSoldFruit(SoldFruitRequest request) {
        fruitRepository.saveSoldFruit(request.getId());
    }

    public List<FruitResponse> showSalesAmount(String name) {
        return fruitRepository.showSalesAmount(name);
    }
}

repository > fruit > FruitRepository.java

package com.group.libraryapp.repository.fruit;

import com.group.libraryapp.dto.fruit.response.FruitResponse;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.List;

@Repository
public class FruitRepository {
    private final JdbcTemplate jdbcTemplate;

    public FruitRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void saveFruitInfo(String name, LocalDate warehousingDate, long price) {
        String sql = "INSERT INTO fruit (name, warehosingDate, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, name, warehousingDate, price);
    }

    public void saveSoldFruit(long id) {
        String sql = "UPDATE fruit SET isSold = 1 WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }

    public List<FruitResponse> showSalesAmount(String name) {
        String sql = "SELECT " +
                "SUM(CASE WHEN isSold = 1 THEN price ELSE 0 END) AS salesAmount, " +
                "SUM(CASE WHEN isSold = 0 THEN price ELSE 0 END) AS nonSalesAmount " +
                "FROM fruit WHERE name = ? GROUP BY name";
        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            int salesAmount = rs.getInt("salesAmount");
            int nonSalesAmount = rs.getInt("nonSalesAmount");
            return new FruitResponse(salesAmount, nonSalesAmount);
        }, name);
    }
}

문제 2

문제 1에서 코드가 분리되면 FruitController / FruitService / FruitRepository 가 생겼을 것입니다.

기존에 작성했던 FruitRepository를 FruitMemoryRepository와 FruitMySqlRepository로 나누고 @Primary 어노테이션을 활용해 두 Repository를 바꿔가며 동작시킬 수 있도록 코드를 변경해보세요!

<aside> 💡 @Qualifier 어노테이션을 사용해도 좋습니다!

</aside>

코드 구현

repository > fruit > FruitRepository.java

public interface FruitRepository {

	void saveFruitInfo(String name, LocalDate warehousingDate, long price);

	void saveSoldFruit(long id);

	List<FruitResponse> showSalesAmount(String name);
}

repository > fruit > FruitMySqlRepository.java

@Repository
@Primary
public class FruitMysqlRepository implements FruitRepository {

	private final JdbcTemplate jdbcTemplate;

	public FruitMysqlRepository(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	@Override
	public void saveFruitInfo(String name, LocalDate warehousingDate, long price) {
		String sql = "insert into fruit (name, warehousing_date, price) values(?, ?, ?)";
		jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
	}

	@Override
	public void saveSoldFruit(long id) {
		String sql = "update fruit set is_sold = CASE WHEN is_sold = 0 THEN 1 ELSE 0 END WHERE id = ?";
		jdbcTemplate.update(sql, id);
	}

	@Override
	public List<FruitResponse> showSalesAmount(String name) {
		String sql = "SELECT " +
                "SUM(CASE WHEN isSold = 1 THEN price ELSE 0 END) AS salesAmount, " +
                "SUM(CASE WHEN isSold = 0 THEN price ELSE 0 END) AS nonSalesAmount " +
                "FROM fruit WHERE name = ? GROUP BY name";

		return jdbcTemplate.query(sql, (rs, rowNum) -> {
        int salesAmount = rs.getInt("salesAmount");
        int nonSalesAmount = rs.getInt("nonSalesAmount");
        return new FruitResponse(salesAmount, nonSalesAmount);
    }, name);
}