[인프런 워밍업 클럽 스터디] BE 1기 두번째 발자국

[인프런 워밍업 클럽 스터디] BE 1기 두번째 발자국

섹션 3. 역할의 분리와 스프링 컨테이너

  1. 스프링 컨테이너와 스프링 빈

  • @RestController: 강의 중에 다루는 Controller 클래스를 스프링 빈으로 등록한다.

    • 스프링 빈: 스프링 컨테이너 안에 들어간 것. 클래스에 대한 다양한 정보 저장&인스턴스화가 이루어진다. 이때 필요한 의존성이 자동으로 설정되어 JdbcTemplate를 통한 인스턴스화가 가능하다.

  • @Repository: Repository를 스프링 빈으로 등록한다.

  • @Service: Service를 스프링 빈으로 등록한다.

  1. 스프링 컨테이너의 필요성

  • 만약 필요에 따라 Repository를 구분하여 작성하게 된다면, Service 계층에서 어떤 Repository를 사용할지 수정해 주어야 한다. 큰 규모의 프로젝트에서는 담당하는 Repository를 변경하는 것이 번거롭기 때문에 Service 계층의 코드를 바꾸지 않고 Repository만을 변경할 방법을 고안해야 한다.

    • 이것에 대한 해결책이 스프링 컨테이너인 것.

  • 제어의 역전(IoC, Inversion of Control): 컨테이너가 필요한 Repository를 선택하고, Service를 만들어 주는 것.

  • 의존성 주입(Dependency Injection): Service를 만들 때 Repository 중 하나를 선택해 넣어주는 과정

    • @Primary: 우선권 제어. 이 어노테이션이 붙은 Repository를 선택한다.

  1. 스프링 빈을 다루는 법

     

    • 스프링 빈으로 등록하기

       

      • @Service@Repository: 개발자가 만든 클래스를 스프링 빈으로 등록할 때 사용

      • @Configuration@Bean: 외부 라이브러리나 프레임워크에 만들어져 있는 클래스를 스프링 빈으로 등록할 때 사용

        • @Configuration : 클래스에 붙이는 어노테이션. @Bean을 사용할 때 함께 사용.

           

        • @Bean : 메소드에 붙이는 어노테이션. 메소드에서 반환되는 객체를 스프링 빈에 등록.

      • @Component: 컨트롤러, 서비스, 리포지토리 외의 추가적인 클래스를 스프링 빈으로 등록할 때 사용. 주어진 클래스를 컴포넌트로 간주한다. 스프링 서버가 사용될 때 자동으로 감지된다.

         

    • 스프링 빈을 주입받기

       

      • 생성자 이용하기: 가장 간단, 권장되는 방법

        • ex. JdbcTemplate jdbcTemplate

      public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
      }
      • setter 사용하기: final 키워드 제거, 메소드에 @Autowired 붙이기

      • 필드에 직접 주입: 필드 위에 바로 @Autowired를 적어준다.

         

      • @Qualifier: @Primary 어노테이션이 없는 상황에서 주입받는 쪽이 특정 스프링 빈을 선택함.

        • @Primary @Qualifier를 모두 사용하고 있다면?: @Qualifier를 사용한다.


섹션 4. 생애 최초 JPA 사용하기

1-1. 문자열 SQL을 직접 사용하는 것의 한계

  • 문자열 작성에 실수가 있을 수 있고, 이를 인지하는 시점이 느림: 런타임 오류로 이어질 수 있다.

  • 특정 DB에 종속적: 특정 DB를 사용하다가 다른 종류의 DB로 바꿔야한다면 번거롭다.

  • 많은 반복작업: 많은 수의 쿼리를 작성해야 하고, SELECT 쿼리 시에는 필드 매핑이 번거롭다.

  • DB의 테이블과 객체의 패러다임이 다르다: DB는 절차지향적이라면, 객체는 객체지향적.

     

     

     

     

    1-2. 문자열 SQL의 한계에 대한 해결책

    • JPA(Java Persistence API): 객체와 관계형 데이터베이스의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙(interface)

      • Hibernate: JPA는 API이기 때문에 규칙일 뿐이고, 이 규칙대로 코드를 작성한 가장 유명한 프레임워크

      • Spring Data JPA: SQL을 작성하지 않아도 쿼리가 나갈 수 있도록 자동으로 처리해준다.

         

        • Spring Data JPA를 이용해 데이터를 생성, 조회, 수정, 삭제하기

        • 스프링은 JpaRepository<Entity, ID>를 구현 받는 Repository에 대해 자동으로 SimpleJpaRepository 기능을 사용할 수 있게 해 준다. SimpleJpaRepository에 있는 대표적인 메소드는 다음과 같다.

          • save: 주어지는 객체를 저장하거나 업데이트

          • findAll: 주어진 객체가 매핑된 테이블의 모든 데이터 가져오기

          • findById: id를 기준으로 특정한 1개의 데이터 가져오기

        • 복잡한 JPA 코드를 직접 사용하는 게 아니라, 추상화된 기능으로써 사용하게 된다.

  1. Spring Data JPA를 이용해 다양한 쿼리 작성하기

  • ex. 유저 삭제하기: 이름을 이용해 유저 존재 여부 확인/ 유저가 존재한다면 delete 쿼리 날리기

  • UserRepository 인터페이스

    • User: 이름을 기준으로 유저 데이터 조회해서 유저 객체 반환. 유저가 없다면 null이 반환됨

    • findByName: 함수 이름만 작성하면, 알아서 select * from user where name = ?라는 SQL이 조립된다.

      • find: 1개의 데이터를 가져옴.

      • By: 뒤에 붙는 필드 이름으로 SELECT 쿼리의 WHERE 문이 작성된다

         

public interface UserRepository extends JpaRepository<User, Long> { 
  User findByName(String name); 
}
  • UserRepository: 기본적으로 들어있는 delete 메소드를 사용한다.

public void deleteUser(String name) { 
  User user = userRepository.findByName(name); 
  if (user == null) { 
    throw new IllegalArgumentException(); 
  } 
  userRepository.delete(user); 
}
  • Spring Data JPA의 추가적인 쿼리 작성법

    • By 앞

      • find: 반환 타입은 객체 or Optional<타입>

      • findAll: 쿼리의 결과물이 N개인 경우 사용 반환 타입은 List<타입> 이다.

      • exists: 쿼리 결과가 존재하는지를 확인. 반환 타입은 boolean이다.

      • count: SQL의 결과 개수를 센다. 반환 타입은 long이다.

    • By 뒤: 필드 이름이 들어가고, 이들을 And나 Or로 조합할 수 있다. 동등 조건(=) 외 다양한 조건 활용도 가능함.

      • findAllByNameOrAge 라 작성하게 되면, select * from user name = ? or age = ? 라는 쿼리가 나간다.

 

  1. 트랜잭션의 필요성과 스프링에서 트랜잭션을 제어하는 방법

  • 트랜잭션: 여러 SQL을 사용해야 할 때 한 번에 성공시키거나, 하나라도 실패하면 모두 실패시키는 기능

    • commit: 트랜잭션 시작 후 사용된 SQL을 성공적으로 반영한다

    • rollback: 트랜잭션 시작 후 사용된 SQL을 모두 취소한다.

  • 스프링에서 트랜잭션을 제어하는 방법: 대상 메소드에 @Transactional 어노테이션을 붙여준다.

    • @Transactional 어노테이션

      • 데이터의 변경이 없고, 조회 기능만 있을 때는 @Transactional(readOnly = true)로 설정할 수도 있다.

      • Unchecked Exception에 대해서만 롤백이 일어난다. (IOException과 같은 Checked Exception에 대해서는 일어나지 않음)

    • 영속성 컨텍스트: 테이블과 매핑된 Entity 객체를 관리/보관하는 역할 수행

       

      • 스프링의 경우, 트랜잭션을 사용하면 영속성 컨텍스트가 생겨 나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다.

        • 영속성 컨텍스트의 특징

          • 변경 감지 (Dirty Check): Entity는 명시적으로 save 해주지 않아도 알아서 변경을 감지하여 저장한다.

          • 쓰기 지연: 트랜잭션이 commit 되는 시점에 SQL을 모아서 한 번만 날린다.

          • 1차 캐싱: ID를 기준으로 Entity를 기억한다.

             


    섹션 5. 책 요구사항 구현하기

    책 생성, 대출, 반납 API를 온전히 개발하며 지금까지 다루었던 모든 개념을 실습해 본 다.

  1. 책 생성, 대출, 반납 API 개발하기

     

    • 책 생성 API:

      book 테이블을 설계하고, Book 객체를 만들고, Repository, Service, Controller, DTO를 만든다.

       

      • book 테이블 명세:

create table book( 
  id bigint auto_increment, 
  name varchar(255), 
  primary key (id) 
);
  • 책 대출 API: 어떤 유저가 어떤 책을 반납했는지 확인할 수 있는 user_loan_history 테이블을 추가로 만들고, 책이 대출되었는지 확인 후 대출되지 않아야 대출할 수 있다.

    • user_loan_history 테이블 명세:

create table user_loan_history ( 
  id bigint auto_increment, 
  user_id bigint, 
  book_name varchar(255), 
/* 
유저가 빌린 책을 대출 중인지, 반납 완료했는지 확인하는 필드이다. 
이 필드에 0이 들어가 있으면 대출 중인 것이고, 1이 들어가 있으면 반납한 것이다.
tinyint는 객체와 매핑되면 true인 경우 1이, false인 경우 0이 저장된다. 
*/
  is_return tinyint(1), 
  primary key (id) 
)
  • user_loan_history 테이블에 대응되는 객체 loanhistory 패키지와 Repository를 만든다.

  • 서비스 계층: 책 이름으로 책을 가져오고, 책이 없는 경우를 확인해 예외처리한다. Book 객체를 확인하고 대출 기록을 확인한다. 대출되지 않았다면 유저 객체를 가져오고 대출 기록을 저장한다.

  • 책 반납 API

    • 대출 기능에서 사용하는 HTTP Body 스펙이 유저 이름과 책 이름으로 동일하지만, 새로운 DTO를 만들어 주는 것이 추후 side-effect의 발생을 줄인다.

    • 유저 이름을 찾아 유저 객체를 받아오고 유저 아이디와 책 이름으로 대출 기록 객체를 받는다. 해당 책의 is_return 값을 '1'로 바꾼다.


    <2주차 학습 내용 회고>

  • API 개발을 계속해서 더 객체지향적으로 발전시키는 법을 배웠다. 아직도 SQL을 API에 가져와 쓰는 것이 익숙하지는 않지만, 이번주 학습 내용에 포함된 JPA를 자유롭게 다룰 수 있으면 훨씬 보기 쉽고 안정적인 서버를 개발할 수 있을 것 같다.

  • 특히 책 대출 기능 개발 부분이 어려웠는데, exists 메소드를 작성할 때 exist로 오타를 내서 스프링 서버가 동작하지 않아 아찔했던 기억이 있다. 여러 개의 클래스, 패키지 등을 다루는 만큼 더 꼼꼼하게 작성하도록 주의를 기울여야겠다.

  • 이번주는 평일에 이런저런 일들이 많아 제시간에 강의를 듣지 못한 날이 있다. 다음주가 마지막인만큼 하루에 들어야 할 강의는 꼭 그 날 해결할 수 있도록 할 것이다.

     

     

    <미션>

  • 과제4

    • 과일 가게 운영을 표현하는 API를 만들기

    • 3단 분리를 해서 만들었는데, 알고 보니 과제6이 관련된 과제였어서 조금 후회되었다. 그래도 작성한 코드가 완벽하게 분리된 것은 아니어서 다음 과제에서는 더 다듬어진 코드로 만들 예정이다.

    • SQL문을 통해서는 테이블에서 필요한 값을 뽑아내는 것이지, SQL문을 통해 모든 것이 더 계산된 결과를 받는 것이 아니다. 필요한 계산은 Repository 내에서 일어나도록 코드를 작성한다.

       

       

  • 과제5

    • 클린 코드로 리팩토링하기

    • 강의 중에서도 클린 코드에 대한 언급이 간략하게 있지만, 직접 구글링으로 클린 코드에 대해 자세히 정리한 글을 읽으며 다시 한번 복습했다. 주어진 과제 코드를 리팩토링하면서 앞서 읽은 클린 코드에 대한 지식을 적용해 보려고 노력했다. (변수명, 클래스명, 함수명 짓기와 함수의 역할 분리)

    • 과제 코드는 간단한 동작을 하는 것이기 때문에 클래스와 메인 함수를 같은 java 파일 내에서 작성했지만, 규모가 더 큰 프로젝트라면 지금 하고 있는 도서 관리 어플리케이션처럼 많은 패키지와 클래스로 나눠서 작성하여 더 클린하게 코드를 작성하도록 유의해야 한다.

    • 클린 코드에 대한 것은 어렴풋이 알고 있었던 것이지만 의식해서 '클린 코드로 만들어보자'고 작성한 것은 이번 과제가 처음이라 새로운 경험이었다. 앞으로도 클린 코드 작성 원칙을 잊지 않도록 유념해야겠다.

       

       

  • 프로그래밍 과제가 많아 힘들었지만, 해결했을 때의 뿌듯함은 역시 프로그래밍 과제만 한 것이 없는 것 같다. 남은 과제들도 모두 잘 수행해 보겠다.

댓글을 작성해보세요.

채널톡 아이콘