728x90

4일차에 만들었던 API를 Controller - Service - Repository로 나누어 보았다.

4일차 과제 보기 👉 https://ddonydev.tistory.com/74  

 

  • Controller
@RestController
public class FruitController {

    private final FruitService fruitService;

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

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


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

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

 

  • Service
@Service
public class FruitService {

    private final FruitRepository fruitRepository;

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

    public FruitResponse selectFruits(String name) {
        return fruitRepository.selectFruits(name);
    }

    public void insertFruits(FruitRequest fruitRequest){
        fruitRepository.insertFruits(fruitRequest.getName(), fruitRequest.getWarehousingDate(), fruitRequest.getPrice());
    }

    public void updateFruits(FruitUpdateRequest request) {

        boolean isExist = fruitRepository.isExist(request.getId());

        if (isExist) {
            throw new RuntimeException("존재 하지 않는 과일입니다.");
        }

        fruitRepository.updateFruits(request.getId());

    }
}

 

  • Repository
@Repository
public class FruitRepository {

    private final JdbcTemplate jdbcTemplate;

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

    public FruitResponse selectFruits(String name) {
        String sale = "SELECT SUM(price) as salesAmount FROM fruits WHERE isSold = true AND name = ?";
        String notSale = "SELECT SUM(price) as notSalesAmount FROM fruits WHERE isSold = false AND name = ?";

        long salesAmount = jdbcTemplate.queryForObject(sale, (rs, rowNum) -> rs.getLong("salesAmount"), name);
        long notSalesAmount = jdbcTemplate.queryForObject(notSale, (rs, rowNum) -> rs.getLong("notSalesAmount"), name);

        return new FruitResponse(salesAmount, notSalesAmount);
    }

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

    public boolean isExist(long id) {
        String readSql = "SELECT * FROM fruits WHERE ID = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }

    public void updateFruits(long id) {
        String updateSql = "UPDATE fruits SET isSold = true where id = ?";
        jdbcTemplate.update(updateSql, id);
    }

}

 

전 코드에서는 Controller에서 SQL 처리까지 다 해주었는데 Service에서는  isExist와 같이 "존재하는지 판별하기" 등 주요 비즈니스 처리를 해주었고, Repository에서는 DB에 접근 할 수 있도록 역할을 나누었다. 그리고 이전 코드에서는 Controller에서 JDBCTemplate를 주입받아 사용하였는데 이번 코드에서는 Repository에서 JDBCTemaplate를 주입받아 DB와 통신하고 Service, Controller에는 결과를 return해주었다.

 

 

  • Interface
public interface FruitRepository {

    FruitResponse selectFruits(String name);

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

    boolean isExist(long id);

    void updateFruits(long id);

}

 

  • FruitMemoryRepository
@Repository
public class FruitMemoryRepository implements FruitRepository{
    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public FruitResponse selectFruits(String name) {
        String sale = "SELECT SUM(price) as salesAmount FROM fruits WHERE isSold = true AND name = ?";
        String notSale = "SELECT SUM(price) as notSalesAmount FROM fruits WHERE isSold = false AND name = ?";

        long salesAmount = jdbcTemplate.queryForObject(sale, (rs, rowNum) -> rs.getLong("salesAmount"), name);
        long notSalesAmount = jdbcTemplate.queryForObject(notSale, (rs, rowNum) -> rs.getLong("notSalesAmount"), name);

        return new FruitResponse(salesAmount, notSalesAmount);
    }

    @Override
    public void insertFruits(String name, LocalDate warehousingDate, long price) {
        String sql = "INSERT INTO fruits (name, warehousingDate, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, name, warehousingDate, price);
    }

    @Override
    public boolean isExist(long id) {
        String readSql = "SELECT * FROM fruits WHERE id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }

    @Override
    public void updateFruits(long id) {
        String updateSql = "UPDATE fruits SET isSold = true WHERE id = ?";
        jdbcTemplate.update(updateSql, id);
    }
}

 

  • FruitMysqlRepository
@Repository
@Primary
public class FruitMySqlRepository implements FruitRepository{
    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public FruitResponse selectFruits(String name) {
        String sale = "SELECT SUM(price) as salesAmount FROM fruits WHERE isSold = true AND name = ?";
        String notSale = "SELECT SUM(price) as notSalesAmount FROM fruits WHERE isSold = false AND name = ?";

        long salesAmount = jdbcTemplate.queryForObject(sale, (rs, rowNum) -> rs.getLong("salesAmount"), name);
        long notSalesAmount = jdbcTemplate.queryForObject(notSale, (rs, rowNum) -> rs.getLong("notSalesAmount"), name);

        return new FruitResponse(salesAmount, notSalesAmount);
    }

    @Override
    public void insertFruits(String name, LocalDate warehousingDate, long price) {
        String sql = "INSERT INTO fruits (name, warehousingDate, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, name, warehousingDate, price);
    }

    @Override
    public boolean isExist(long id) {
        String readSql = "SELECT * FROM fruits WHERE id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }

    @Override
    public void updateFruits(long id) {
        String updateSql = "UPDATE fruits SET isSold = true WHERE id = ?";
        jdbcTemplate.update(updateSql, id);
    }
}

 

이렇게 위의 요구사항과 같이 Interface로 나누고 FruitMemoryRepositoryFruitMySqlRepositoryFruitRepository를 구현하도록 하였다. 이렇게 되면 Service에 코드 수정 없이 RepositoryMemory로 사용할지 Mysql로 사용할지 사용할 수 있다.

 

💡그렇다면 어떻게 사용할까?

 

현재 FruitMySqlRepository를 보면 @Primary 어노테이션이 있다. 이 어노테이션은 "FruitMySqlRepository을 사용할거야~"라고 알려주는 어노테이션이다. 만약 @Primary가 없다면 Service 코드의 생성자 부분에서 오류가 나게된다.

 

다음으론 @Qualifier어노테이션이 있다. @Qualifier("fruitMemoryRepository")Service 코드의 생성자 부분에 사용하면 FruitMySqlRepository @Primary가 붙어 있더라도 fruitMemoryRepository를 사용하게 된다.

(스프링에서는 사용자가 직접 적어준 @Qualifier가 우선순위를 가진다.)

@Service
public class FruitService {

    private final FruitRepository fruitRepository;

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

    // 이하 생략
}

 

@Qualifier 어노테이션은 @Qualifier("fruitMemoryRepository") 이런 방식이 아니어도 아래와 같은 방식으로도 사용할 수 있다.

// 서비스 코드
@Service
public class FruitService {

    private final FruitRepository fruitRepository;

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

    // 이하 생략
}

// 레포지토리 코드
@Repository
@Qualifier("fruit")
public class FruitMemoryRepository implements FruitRepository{
    // 이하 생략
}

 

사용하려는 곳에 직접 이름을 지정해주고, 지정해준 이름을 생성자 앞에 적어주는 방식으로도 사용할 수 있다.

 

@Qualifier를 사용시 클래스 이름을 그대로 적어주면 아래와 같이 오류가 났다.

Inspection 'Incorrect autowiring in Spring bean components' has no quick-fixes for this problem.
Click to edit inspection options, suppress the warning, or disable the inspection completely.

 

이는 Spring 프레임워크에서 사용되는 빈(Bean)에 대한 자동 와이어링(Autowiring) 설정이 올바르지 않을 때 발생하는 경고이다. 경고가 "quick-fixes"를 제공하지 않는다는 것은 IDE가 자동으로 이 문제를 해결할 수 있는 방법이 없다는 것을 의미한다. 보통은 해당 빈을 명시적으로 주입하도록 코드를 수정하거나, 인스펙션 설정을 변경하여 경고를 무시하거나 비활성화하는 등의 수동 조치가 필요하다.

라고 chat-gpt가 이야기 해주었다..

 

@Autowired 어노테이션을 추가하거나 IntelliJSettings 메뉴에서 Editor > Inspections로 이동하여 "Incorrect autowiring in Spring bean components" 검색하여 해당 인스펙션의 옵션을 수정하라고 하였다.

 

자세히 강의를 보니 강사님께서 클래스 이름을 FruitMemoryRepository로 작성하셨는데 첫 글자를 소문자로 작성하신것을 보고 바꾸니까 에러가 없어졌다.

 

왜 그런지 아직 잘 모르겠다.. 조금 찾아보아야겠다.

 

[이유를 알아냈다..!]

@Qualifier는 직접 빈으로 등록해준 것이 아니라면 빈으로 설정된 Default 이름을 적어주어야한다.

 

FruitMemoryRepository의 등록된 이름을 확인해보니 아래와 같았다.

 

즉, Default 이름은 첫 글자가 소문자인 이름으로 등록되는 것이었다.

요것도 몰랐다니..아무튼 이번에 알게 되었으니 꼭 기억해야겠다!

728x90

+ Recent posts