🔥새해맞이 특별 라이브 선착순 신청🔥

인프런 워밍업 스터디 클럽 0기 BE - 2주차 발자국

인프런 워밍업 스터디 클럽 0기 BE - 2주차 발자국

강의 수강

  • 학습 내용 요약

스프링 컨테이너

클래스 안에 메소드를 생성해서 그것을 사용하기 위해선, 클래스를 "인스턴스화" 하는게 필요하다.

인스턴스화 한다는 건 간단히 말해서 붕어빵 틀(클래스)을 가지고 붕어빵을 만드는 거다.

지난 1주차에서는 UserController라는 클래스 안에서 api 진입지점으로 사용할 메소드들을 정의했었다.

 

그런데 강사님이 하라는대로 하긴했는데, 생각해보면 UserController를 인스턴스화 한적이 없다.

또한 mysql db와의 연결을 위해 jdbcTemplate이라는걸 사용했었는데,

이 템플릿을 파라미터로 가져와서 사용하려면 어딘가에서 jdbc템플릿이 정의되어있어야 할텐데

그런걸 한 적도 없다.

@RestController
public class UserController {
 private final UserService userService;


  public UserController(JdbcTemplate jdbcTemplate) {
    this.userService = new UserService(jdbcTemplate);
}
}

여기서 나오는 개념이 바로 스프링 컨테이너다.

@RestController의 역할은 크게 두 가지이다.

  1. UserController 클래스를 API의 진입 지점으로 등록

  2. UserController 클래스를 스프링 빈으로 등록

이 스프링 빈이라는 것이 스프링 컨테이너와 관계가 있다.

서버가 시작되면 자동으로 생성되는 것이 컨테이너 라는 건데, 이 컨테이너에

스프링 빈으로 등록된 클래스가 들어가게 된다. 또한 그 클래스의 인스턴스화도 함께 이뤄진다.

 

서버 시작 -> 기본 스프링 빈 등록 -> 개발자 설정 스프링 빈 등록 -> 해당 클래스들에게 필요한 의존성들이 자동 설정

 

따라서 UserController를 인스턴스화 해주지 않아도 메소드를 사용할 수 있었던 거다.

또한 jdbc템플릿도 이미 스프링 빈으로 등록되었기 때문에 우리가 따로 정의해놓지 않았지만

사용이 가능했다.

 

스프링 빈으로 굳이 왜 등록할까?

  1. 코드가 줄어드는 것 같다. -> 그닥 드라마틱하진 않다.

다양한 장점들이 있겠지만, 그냥 코드를 직접 짜는 입장에서 봤을 때는 인스턴스화를 new로 일일히 해주는게 귀찮았는데 스프링 빈으로 등록하면, 그냥 생성자에서 불러주기만 하면 돼서 좋은 것 같다.

  1. Repository에 변경사항이 생겼다고 가정할 때, service단의 변경도 필수가 된다. -> 이건 사고다

왜냐하면, 지금은 레포지토리를 부르는 게 한군데 밖에 없었지만 실제 현업에서는 레포지토리를 사용하는 코드 전부를 고쳐야 할 것이기 때문이다.

  1. 스프링 빈으로 등록하면, 컨테이너가 "의존성 주입"을 통해 변경된 레포지토리를 "선택"하도록 할 수 있다. -> 아주 좋다.

참고) 의존성 주입을 위한 어노테이션

  • @Primary

  • @Qualifier

     

참고) 스프링 빈 등록하는 어노테이션

  • Repository : @Repository

  • Service : @Service

  • controller: @RestController

문자열 sql 대신 JPA 사용하기

문자열 sql을 작성한 후, jdbctemplate을 이용해 mysql로 쿼리를 날리는 방식이 나쁘지 않다. 그런데 실제로 코드 짜면서 느낀거지만, 쿼리 하나가 좀 길다보니 간혹 오타를 낼 때 오류 잡는게 귀찮았다.

-> 실수를 런타임 때 발견하게 되어, 인지가 느리다.

또 sql의 문법이 문제다. sql을 만든 회사에 따라서 문법이 다 조금씩 다른데,

이럴일이 흔한지는 모르겠지만 Mysql을 쓰다가 갑자기 Mssql로 변경한다면

지옥일거다.

-> 문자열 sql 쿼리를 모두 수정하는 불상사가 발생

테이블에 칼럼이 5개만 해도 헷갈리는데, 그 이상이라고 한다면 데이터 하나당 테이블 칼럼으로 매칭해주는게 복잡해진다.

-> 귀찮고 복잡하다.

그래서 우리는 문자열 sql 대신 JPA를 사용할 수 있다.

JPA란 데이터를 영구적으로(영속성) 보관하기 위해 자바 진영에서 정해진 규칙이다. 쉽게 말해 이 JPA를 사용해 코드를 작성하면 객체와 관계형 db(mysql등등) 테이블을 짝지어 영구적으로 저장이 가능하다.

JPA를 사용하는 과정

  1. 최초에 JPA를 적용하기 위한 옵션 설정. jdbc사용을 위해 만들었던 application.yml 에 코드 추가.

  2. 테이블에 매핑되는 객체를 만들기. @Entity,@Id,@Column등 어노테이션 사용.


    주의) 기본 생성자 추가.

  3. 인터페이스를 만들고, jpaRepository를 상속받게 한다. 테이블의 매핑 객체와, 테이블 id의 타입을 넣어준다.

public interface UserRepository extends JpaRepository<User, Long> {
}
  1. 우리가 만든 인터페이스는 jparepository를 상속받았기 때문에, save나 findAll 등 기본 내장 메소드를 쓸 수 있다. 기본 메소드 외에 쓰고 싶으면, 이 인터페이스에 추가를 해주자.

     

     

    • 회고

     

     

     

    : 모든 개념을 공부할 때는 굳이?왜? 라는 의문을 가져봐야 할 것 같다. 굳이 스프링 빈을 왜 쓰는지, 그냥 인스턴스화 시키는것보다 스프링빈으로 등록하는 장점은 무엇인지 등등 말이다.


    : 일단 기본적인 기능완성에 초점을 두고, 더 좋은 코드. 더 효율적인 코드를 고민해보면서 다양한 어노테이션을 제대로 이해해야 겠다.

    미션

  • 이번주 미션 해결 과정

    • 6일차 과제 
      1번 문제는 그냥 강사님 하신 코드 보고 그냥 똑같이 컨트롤러,서비스,레포지토리로 분류하는거라 간단히 해결했다.
      2번 문제도 mysql로 하는건 간단히 했던것 같은데, 오히려 그냥 리스트 객체로 레포지토리 만드는게 어려웠다.


      이 문제는 서비스단에, getFruit메소드와 memory레포지토리의 메소드가 핵심이였다.



      (지금 생각해보니 코드를 좀 이상하게 짠 것 같다.)
      - 메모리 레포지토리
      메모리 레포지토리의 getFruit메소드에서는 서버가 돌아가는동안 만들어진 과일 정보를 담은 fruits라는 리스트를 가지고 있는데,
      이 리스트를 돌며 과일의 이름을 가져와 사용자가 입력한 과일 이름이랑 일치하는 객체를 찾는다.
      일치할 경우 test라는 이름의 리스트에, 해당 객체의 가격정보와 판매여부를 담은 객체를 FruitResponse에서 받아와 저장한 후 이를 리턴한다.
      - 서비스단의 getFruit메소드


      이 리턴값을 fruitList에 받은 후, 리스트를 돌며 판매여부를 확인한 뒤 팔렸으면 salesTotal에, 안팔렸으면 notSalesTotal변수에 가격 정보를 누적하고 이를 객체로 감싸 반환하게 코드를 짰다.

    public FruitFinalResponse getFruit(String name){
    
            //가져온 데이터 결과 rs 를 우리가 원하는 userResponse형태로 바꿔줌.이거는 객체 참조값인데, 전체에서 반환하는건 결국 query로 감싸서 나오므로 리스트 타입.
            boolean isFruitNameNotExist = fruitRepository.isFruitNameNotExist(name);
    
            if(isFruitNameNotExist){
                throw new IllegalArgumentException();
            }else{
    
                List<FruitResponse> fruitList = fruitRepository.getFruit(name);
                long salesTotal=0;
                long notSalesTotal=0;
                for(int i=0; i<fruitList.size();i++){
                    if(fruitList.get(i).isSell()){
                        salesTotal = salesTotal + fruitList.get(i).getSalesAmount();
    
                    }else{
                        notSalesTotal = notSalesTotal + fruitList.get(i).getSalesAmount();
    
                    }
                }
    
                return new FruitFinalResponse(salesTotal,notSalesTotal);
            }
    
        }
    public List<FruitResponse> getFruit(String name){
            List<FruitResponse> test = new ArrayList<>();
            for (Fruit fruit : fruits) {
               if(fruit.getFruitName().equals(name)) {
                   FruitResponse fruitResponse = new FruitResponse(fruit.getPrice(),fruit.isSell());
                   test.add(fruitResponse);
               }
            }
            return test;
        }
    
  • 7일차 과제
    1번 문제는 JPA를 활용하는 강사님 코드를 참고해서 쉽게 기존의 코드를 고쳐서 해결했다.
    2번 문제는 고민을 좀 했었는데, 다행히 기본 JPA메소드 중에 countByName이라는게 있었고 이걸 활용하면


    테이블에서 쿼리로 들어온 과일이름 (=name)을 기반으로 count를 해준다.

   public fruitNumResponse getFruitNum(String name){
        return new fruitNumResponse(fruitRepository.countByName(name));
    }

3번 문제는 쿼리로 들어온 option의 문자열을 가지고, if문 처리를 하는게 핵심이였다.

찾아보니 findByPriceGreaterThanEqual,findByPriceLessThanEqual 요런 메소드들이 있어서 쉽게 데이터를 뽑을 수 있었다.
(나중에 강사님 코멘트를 보니 enum이라는걸 활용하면 이렇게 문자열로 안해도 된다는데 확인해봐야겠다.)

 

그렇게 가져온 데이터들의 판매여부를 확인한 후, 판매되지 않은것만 가져와야 하기 때문에 false인 객체들만 notSellFruits에 담아 반환하도록 코드를 짰다.

public List<FruitListResponse> getFruitList(String option,Long price){
        Collection<Fruit> fruits;
        if(option.equals("GTE")){
            fruits = fruitRepository.findByPriceGreaterThanEqual(price);

        }else{
            fruits = fruitRepository.findByPriceLessThanEqual(price);
        }

        List<FruitListResponse> fruitList = fruits.stream().
                map(fruit1 -> new FruitListResponse(fruit1.getName(), fruit1.getPrice(),fruit1.getWarehousingDate(),fruit1.isSell()))
                .collect(Collectors.toList());

        List<FruitListResponse> notSellFruits = new ArrayList<>();

        for (FruitListResponse fruitListResponse : fruitList) {
           if(fruitListResponse.isSell()==false) {
               notSellFruits.add(fruitListResponse);

           }

        }
        return notSellFruits;


    }
  • 미션 회고
    기억해야 할 오류1)
    자바의 카멜 표기법이 DB에서는 (언더바)로 변환된다는걸 몰라서 발생한 오류였다.
    warehousingDate이라는 변수명을 필드명으로 지정했었는데, 한참 오류가 떠서 구글링하다가 원인을 발견해서 mysql 칼럼명을 warehousing_Date으로 바꾸어 해결할 수 있었다.

    기억해야 할 오류2) query did not return a unique result: 2
    이 오류는 findByName을 할 때 데이터가 여러갠데 받아오는 리턴 타입이 문제여서 발생했다. 원래는 List으로 받았는데 Collection으로 고쳐서 해결됐다. 근데 나는 List랑 Collection차이를 모르니까 공부해야겠다...

    ++ 미니 프로젝트 회고)
    image바로바로 하려니까 헷갈려서, 엑셀에 필요 조건들이랑 스펙을 정리하고 코드를 짜는 중이다.
    그리고 생각보다 미니 프로젝트에 제시된 것만으로는 코드를 바로 짤 수가 없었던 것 같다.
    어떤식으로 테이블을 구성할지, 또 어떤 정보들을 쿼리 또는 바디로 받아올지 등등 하나부터 열까지 내가 정해야 되니까


    좀 어렵긴 한데, 그래도 재밌다!!

댓글을 작성해보세요.

채널톡 아이콘