์ถ์ฒ
https://inf.run/XKQg
์คํ๋ง ์ปจํ ์ด๋์ ์๋ฏธ์ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํด ํ์ตํ์ฌ ๊ณผ์ ๋ฅผ ์ํํ๋ค.
์คํ๋ง์ ์๋ฐ ๊ธฐ๋ฐ์ ํ๋ ์์ํฌ๋ก, ์ฌ๋ฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
๊ทธ ์ค์์๋ IoC(์ ์ด์ ์ญ์ ) ์ปจํ
์ด๋๋ ๊ฐ์ฒด์ ์์ฑ, ์๋ฉธ๊ณผ ๊ฐ์ ์๋ช
์ฃผ๊ธฐ ๊ด๋ฆฌ๋ฅผ ๋ด๋นํ๋๋ฐ, ์ด๋ฅผ ์คํ๋ง ์ปจํ
์ด๋๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์คํ๋ง ์ปจํ
์ด๋๋ ๋น(Bean)์ด๋ผ ๋ถ๋ฆฌ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ , ํ์ํ ๊ณณ์ ์ฃผ์
(Dependency Injection)ํด์ฃผ๋ ์ญํ ์ ํ๋ค.
์ด๋ฅผ ํตํด ์ฝ๋์ ์ ์ฐ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ํฅ์๋๋ฉฐ, ๊ฐ์ฒด ๊ฐ์ ์์กด์ฑ์ ๋ฎ์ถ ์ ์๋ค.
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);
}
}
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);
}
}
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
๋ ์ ํ๋ฆฌ์ผ์ด์
์คํ ์ค์ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ๋ฉฐ, 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
๋ฅผ ํค๋ก ์ฌ์ฉํ์ฌ ๊ณ ์ ํ ์๋ณ์๋ฅผ ๋ถ์ฌํ๊ณ , ๊ณผ์ผ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๋ถํ์ฌ ์ ์ฅํจ์ผ๋ก์จ ๋ฉ๋ชจ๋ฆฌ ์์์ ๊ฐํธํ๊ฒ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ ์ ์์ด ์ฌ์ฉํ๋ค.
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);
}
}
์คํ๋ง์์ ์ฌ๋ฌ ๊ฐ์ ๋์ผํ ํ์
์ 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์ด ์ฃผ์
๋๋ค.
์คํ๋ง์ ๋ค์ํ ์ด๋
ธํ
์ด์
์ ์ ๊ณตํ๋ฉด์ ์ ์ฐํ๊ณ ํ์ฅ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ ํ๊ฒฝ์ ์ ๊ณตํ๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ฐ ์์ฐ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํฌ ์ ์์์ ๋๊ผ๋ค.
์ด์ ๋ ํ
์คํธ ์ฝ๋ ์์ฑ๊ณผ ์์ธ ์ฒ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ํ๋ก์ ํธ ๊ตฌ์กฐ์ ๋ํ ๊ณ ๋ฏผ์ ํตํด ์ฝ๋์ ์์ฑ๋๋ฅผ ๋์ด๋ ๋ฐฉํฅ์ผ๋ก ๋ ๋ฐ์ ํด ๋๊ฐ๊ณ ์ถ๋ค.