๐ŸŽฝ ์ธํ”„๋Ÿฐ ์›Œ๋ฐ์—… ํด๋Ÿฝ 0๊ธฐ ๋ฐฑ์—”๋“œ ์—ฌ์„ฏ ๋ฒˆ์งธ ๊ณผ์ œ

pucaยท2024๋…„ 2์›” 26์ผ
0

์ธํ”„๋Ÿฐ

๋ชฉ๋ก ๋ณด๊ธฐ
6/7
post-thumbnail

์ถœ์ฒ˜
https://inf.run/XKQg

์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์˜ ์˜๋ฏธ์™€ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ํ•™์Šตํ•˜์—ฌ ๊ณผ์ œ๋ฅผ ์ˆ˜ํ–‰ํ–ˆ๋‹ค.

โ›ณ๏ธ Spring Container๋ž€?

์Šคํ”„๋ง์€ ์ž๋ฐ” ๊ธฐ๋ฐ˜์˜ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
๊ทธ ์ค‘์—์„œ๋„ IoC(์ œ์–ด์˜ ์—ญ์ „) ์ปจํ…Œ์ด๋„ˆ๋Š” ๊ฐ์ฒด์˜ ์ƒ์„ฑ, ์†Œ๋ฉธ๊ณผ ๊ฐ™์€ ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š”๋ฐ, ์ด๋ฅผ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๋Š” ๋นˆ(Bean)์ด๋ผ ๋ถˆ๋ฆฌ๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ•„์š”ํ•œ ๊ณณ์— ์ฃผ์ž…(Dependency Injection)ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์œ ์—ฐ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํ–ฅ์ƒ๋˜๋ฉฐ, ๊ฐ์ฒด ๊ฐ„์˜ ์˜์กด์„ฑ์„ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ๋‹ค.


โ›ณ๏ธ FruitController

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class FruitController {

    private final FruitService fruitService;

    @PostMapping("/api/v1/fruit")
    public void saveFruit(@RequestBody FruitInfoRequest request) {
        fruitService.saveFruit(request.name(), request.warehousingDate(), request.price());
    }

    @PutMapping("/api/v1/fruit")
    public void updateFruit(@RequestBody FruitIdRequest request) {
        fruitService.updateFruit(request.id());
    }

    @GetMapping("/api/v1/fruit/stat")
    public FruitAmountResponse getAmount(@RequestParam String name) {
        return fruitService.getAmount(name);
    }
}

โ›ณ๏ธ FruitService

import java.time.LocalDate;
import org.springframework.stereotype.Service;

@Service
public class FruitService {
    private final FruitRepository fruitRepository;

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

    public void saveFruit(String name, LocalDate warehousingDate, long price) {
        fruitRepository.saveFruit(name, warehousingDate, price);
    }

    public void updateFruit(long id) {
        boolean isFruitNotExist = fruitRepository.isFruitNotExist(id);
        if (isFruitNotExist) {
            throw new IllegalArgumentException("ํ•ด๋‹นํ•˜๋Š” ๊ณผ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
        }

        fruitRepository.updateFruit(id);
    }

    public FruitAmountResponse getAmount(String name) {
        return fruitRepository.getAmount(name);
    }
}

โ›ณ๏ธ FruitRepository

import java.time.LocalDate;

public interface FruitRepository {
    void saveFruit(String name, LocalDate warehousingDate, long price);

    boolean isFruitNotExist(long id);

    void updateFruit(long id);

    FruitAmountResponse getAmount(String name);
}

FruitRepository๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜ํ•˜์—ฌ ๊ณผ์ผ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ถ”์ƒํ™”ํ–ˆ๋‹ค.


๐ŸŒฑ FruitMemoryRepository

FruitMemoryRepository๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์ค‘์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•˜๋ฉฐ, FruitRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค.
์ด ํด๋ž˜์Šค๋Š” @Repository ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด Bean์œผ๋กœ ๋“ฑ๋ก๋œ๋‹ค.


์ฐธ๊ณ 
๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜์˜ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„ํŽธํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ์‹œ์ž‘ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๋ฏ€๋กœ ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์‚ฌ์šฉ์— ์ œ์•ฝ์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.


import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
@Primary
public class FruitMemoryRepository implements FruitRepository {
    private final Map<Long, Fruit> store = new HashMap<>();
    private Long sequence = 0L;

    @Override
    public void saveFruit(String name, LocalDate warehousingDate, long price) {
        Fruit fruit = new Fruit(name, warehousingDate, price);
        sequence++;
        store.put(sequence, fruit);
        System.out.println("sequence = " + sequence);
        System.out.println("store.values().toString() = " + store.values());
    }

    @Override
    public boolean isFruitNotExist(long id) {
        Fruit foundFruit = store.get(id);
        return foundFruit == null;
    }

    @Override
    public void updateFruit(long id) {
        Fruit foundFruit = store.get(id);
        foundFruit.changeStatus();
        System.out.println("foundFruit.isSold() = " + foundFruit.isSold());
        System.out.println("foundFruit = " + foundFruit);
    }

    @Override
    public FruitAmountResponse getAmount(String name) {
        long salesAmount = getSalesAmount(name);
        long notSalesAmount = getNotSalesAmount(name);
        System.out.println("salesAmount = " + salesAmount);
        System.out.println("notSalesAmount = " + notSalesAmount);
        return new FruitAmountResponse(salesAmount, notSalesAmount);
    }

    private long getSalesAmount(String name) {
        return store.values().stream()
                .filter(fruit -> fruit.getName().equals(name))
                .filter(Fruit::isSold)
                .mapToLong(Fruit::getPrice)
                .sum();
    }

    private long getNotSalesAmount(String name) {
        return store.values().stream()
                .filter(fruit -> fruit.getName().equals(name))
                .filter(fruit -> !fruit.isSold())
                .mapToLong(Fruit::getPrice)
                .sum();
    }
}

Map์€ ํ‚ค-๊ฐ’ ์Œ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋ฉฐ, ํŠน์ • ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น ๋ฅด๊ฒŒ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด ํšจ๊ณผ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
๋”ฐ๋ผ์„œ, FruitMemoryRepository์—์„œ๋Š” Map์„ ํ…Œ์ด๋ธ”์ฒ˜๋Ÿผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์กฐํ™”ํ–ˆ๋‹ค.

๋˜ํ•œ, sequence๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ ์œ ํ•œ ์‹๋ณ„์ž๋ฅผ ๋ถ€์—ฌํ•˜๊ณ , ๊ณผ์ผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ €์žฅํ•จ์œผ๋กœ์จ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์—์„œ ๊ฐ„ํŽธํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ์‚ฌ์šฉํ–ˆ๋‹ค.

โ–ถ๏ธ ์‹คํ–‰ ๊ฒฐ๊ณผ

1. ๊ณผ์ผ ์ •๋ณด ์ €์žฅ

2. ํŒ”๋ฆฐ ๊ณผ์ผ ์ •๋ณด ๊ธฐ๋ก

3. ํŠน์ • ๊ณผ์ผ ๊ธฐ์ค€์œผ๋กœ ํŒ”๋ฆฐ ๊ธˆ์•ก, ํŒ”๋ฆฌ์ง€ ์•Š์€ ๊ธˆ์•ก ์กฐํšŒ


๐ŸŒฑ FruitMySqlRepository

FruitMySqlRepository๋Š” MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋ฉฐ, JdbcTemplate์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•œ SQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

์ด Repository๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ @Repository ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ๊ณ , ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ํ†ตํ•ด JdbcTemplate์„ ์ฃผ์ž…๋ฐ›๊ณ  ์žˆ๋‹ค.

import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class FruitMySqlRepository implements FruitRepository {
    private final JdbcTemplate jdbcTemplate;

    @Override
    public void saveFruit(String name, LocalDate warehousingDate, long price) {
        String sql = "insert into fruit (name, warehousingDate, price) values (?, ?, ?)";
        jdbcTemplate.update(sql, name, warehousingDate, price);
    }

    @Override
    public boolean isFruitNotExist(long id) {
        String readSql = "select * from fruit where id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }

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

    @Override
    public FruitAmountResponse getAmount(String name) {
        String sql1 = "select sum(price) as salesAmount from fruit where is_sold = 1";
        String sql2 = "select sum(price) as notSalesAmount from fruit where is_sold = 0";

        Long salesAmount = jdbcTemplate.queryForObject(sql1, (rs, rowNum) -> rs.getLong("salesAmount"));
        Long notSalesAmount = jdbcTemplate.queryForObject(sql2, (rs, rowNum) -> rs.getLong("notSalesAmount"));

        return new FruitAmountResponse(salesAmount, notSalesAmount);
    }
}

โ›ณ๏ธ @Primary์™€ @Qulifier

์Šคํ”„๋ง์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋™์ผํ•œ ํƒ€์ž…์˜ Bean์ด ์กด์žฌํ•  ๋•Œ, ํ•ด๋‹น Bean์„ ์ฃผ์ž…๋ฐ›๋Š” ์ฝ”๋“œ์—์„œ ์–ด๋–ค ๋นˆ์„ ์‚ฌ์šฉํ• ์ง€ ์•Œ๋ ค์ฃผ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ @Primary์™€ @Qualifier ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋–ค ๋นˆ์„ ์ฃผ์ž…๋ฐ›์„์ง€ ๋ช…์‹œํ•˜๋„๋ก ํ•œ๋‹ค.

@Repository
@RequiredArgsConstructor
@Primary
public class FruitMemoryRepository implements FruitRepository {
	// ...
}

@Primary ์–ด๋…ธํ…Œ์ด์…˜์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋™์ผํ•œ ํƒ€์ž…์˜ Bean ์ค‘์—์„œ ์šฐ์„ ์ ์œผ๋กœ ์ฃผ์ž…๋ฐ›์•„์•ผ ํ•  Bean์„ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.
FruitMemoryRepository์—์„œ @Primary ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—FruitService์— FruitMemoryRepository ๋นˆ์ด ์ฃผ์ž…๋œ๋‹ค.


@Service
public class FruitService {

    private final FruitRepository fruitRepository;

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

    // ...
}

@Qualifier ์–ด๋…ธํ…Œ์ด์…˜์€ ํŠน์ •ํ•œ Bean์„ ์„ ํƒํ•˜์—ฌ ์ฃผ์ž…๋ฐ›์„ ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, FruitService์—์„œ ํŠน์ • Bean์„ ์„ ํƒํ•˜์—ฌ ์ฃผ์ž…๋ฐ›๊ณ ์ž ํ•  ๋•Œ ์œ„์™€ ๊ฐ™์ด @Qualifier ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Qualifier("fruitMySqlRepository")๋ฅผ ํ†ตํ•ด FruitMySqlRepository๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ ํƒํ•˜์—ฌ ์ฃผ์ž…๋ฐ›๋Š”๋‹ค.
์ด ๊ฒฝ์šฐ, @Qualifier๊ฐ€ @Primary๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น Bean์ด ์ฃผ์ž…๋œ๋‹ค.


โ›ณ๏ธ ๊ฐœ์ธ ํšŒ๊ณ 

์Šคํ”„๋ง์€ ๋‹ค์–‘ํ•œ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ œ๊ณตํ•˜๋ฉด์„œ ์œ ์—ฐํ•˜๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ์„ ๋А๊ผˆ๋‹ค.
์ด์ œ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์™„์„ฑ๋„๋ฅผ ๋†’์ด๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๋” ๋ฐœ์ „ํ•ด ๋‚˜๊ฐ€๊ณ  ์‹ถ๋‹ค.

profile
๐Ÿ€

0๊ฐœ์˜ ๋Œ“๊ธ€