[인프런워밍업스터디_BE_0기] 2주차 정리!

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


[섹션 목표]
1. 좋은 코드가 왜 중요한지 이해하고, 원래 있던 Controller 코드를 보다 좋은 코드로 리팩토링한다.

2. 스프링 컨테이너와 스프링 빈이 무엇인지 이해한다.

3. 스프링 컨테이너가 왜 필요한지, 좋은 코드와 어떻게 연관이 있는지 이해한다.

4. 스프링 빈을 다루는 여러 방법을 이해한다.

스프링 컨테이너와 스프링 빈은 무엇일까?

지난주 우리는 @SpringBootApplication 어노테이션이 다양한 설정을 자동으로 해준다고 배웠는데 이 중 하나가 바로 스프링 컨테이너를 만드는 것으로 이 안에는 클래스들이 들어가게 된다. 그리고 스프링 컨테이너 안에 들어간 클래스를 스프링 빈이라고 부른다. 스프링은 이렇게 컨테이너 안에 있는 클래스를 자동으로 관리해준다.

 

스프링 컨테이너는 왜 필요할까?

기존에 우리가 구현한 코드에서 JdbcTemplate을 활용한 Repository를 다른 Repository로 변경하고자 한다면 기존의 코드를 모두 찾아서 다른 Repository로 변경해야한다! 하지만 스프링 컨테이너가 Repository 중 하나를 선택해 자동으로 적용해준다면 코드를 일일이 찾아서 바꿔줄 필요가 없게 된다! 이러한 개념을 제어의 역전 (Inversion of Control)이라고 한다. 그리고 이렇게 Repository 중 하나를 선택에 적용하는 과정을 의존성 주입 (Dependency Injection) 이라고 한다.

스프링 빈을 다뤄보자!

이전까지 우리는 @RestController, @Service, @Repository 등을 활용하여 클래스를 스프링 빈으로 등록하였다. 이러한 어노테이션은 클래스에 직접 사용하였는데, 외부에서 빈을 등록하는 방법이 있다.

@Configuration 
public class UserConfiguration { 
  @Bean 
  public UserRepository userRepository(JdbcTemplate jdbcTemplate) { 
    return new UserRepository(jdbcTemplate); 
  } 
}

이와 같이 외부에 설정 클래스를 만들어 빈을 등록할 수도 있다. 그럼 이런 방법은 언제 사용할까?

선생님께서는 정답은 없지만 일반적으로 개발자가 직접 만든 클래스는 @Service, @Repository 등을 사용하고, 외부 라이브러리나 프레임워크에 만들어져 있는 클래스를 사용할 때는 @Configuration@Bean 의 조합을 사용하는 편이라고 한다.

 

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


[섹션 목표]
1. 문자열 SQL을 직접 사용하는 것의 한계를 이해하고, 해결책인 JPA, Hibernate, SpringData JPA가 무엇인지 이해한다.

2. Spring Data JPA를 이용해 데이터를 생성, 조회, 수정, 삭제할 수 있다.

3. 트랜잭션이 왜 필요한지 이해하고, 스프링에서 트랜잭션을 제어하는 방법을 익힌다.

4. 영속성 컨텍스트와 트랜잭션의 관계를 이해하고, 영속성 컨텍스트의 특징을 알아본다.

문자열 SQL 사용의 한계점

  1. 문자열을 직접 작성하기 때문에 실수가 나올 수 있는데 이러한 실수가 있을 경우에도 컴파일 시에는 발견하지 못하고 정상 실행되어 런타임중에야 알 수 있다.

  2. SQL도 데이터베이스의 종류마다 문법이 다른데, 문자열로 작성하면 특정 데이터베이스에 의존하게 된다.

  3. 간단한 CRUD 작업을 할 때마다 SQL을 작성해줘야하기 때문에 반복작업이 많다.

  4. 데이터베이스의 테이블과 객체는 패러다임이 다르다.

 

JPA, Hibernate, Spring Data JPA

JPA(Java Persistence API) 로 자바 진영의 ORM(Object-Relational Mapping) 기술 표준을 의미한다. 조금 어려운데 선생님께서는 다음과 같이 쉽게 요약해주셨다.

  • JPA : 객체와 관계형 데이터베이스의 테이블을 매핑해 데이터를 영구적으로 보관하기 위해 Java 진영에서 정해진 규칙

그리고 JPA는 API이기 때문에 말 그대로 '규칙'일 뿐이고, 그 규칙을 실제 코드로 작성한 가장 유명한 프레임워크가 Hibernate이다. 그리고 이들을 활용해서 만들어진 Spring Data JPA를 활용하면 복잡한 JPA 코드를 직접 사용하는 것이 아니라, 추상화된 기능으로써 사용할 수 있게 되는 것이다.

 

Spring Data JPA로 CRUD 작업을 해보자!

// UserServiceV2 클래스..
    public void saveUser(UserCreateRequest request) {
        userRepository.save(new User(request.getName(), request.getAge()));
    }

    public List<UserResponse> getUsers() {
        return userRepository.findAll().stream()
                .map(UserResponse::new)
                .collect(Collectors.toList());
    }

    public void updateUser(UserUpdateRequest request) {
        User user = userRepository.findById(request.getId())
                .orElseThrow(IllegalArgumentException::new);

        user.updateName(request.getName());
        userRepository.save(user);
    }

    public void deleteUser(String name) {
        User user = userRepository.findByName(name)
                .orElseThrow(IllegalArgumentException::new);

        userRepository.delete(user);
    }

 기존 JdbcTemplate을 사용했던 코드를 위와 같이 변경하였다!

트랜잭션은 왜 필요하고, 스프링에서 어떻게 사용할까?

트랜잭션이란 쪼갤 수 없는 업무의 최소 단위로 여러 SQL을 사용해야할 때 한 번에 성공시키거나, 하나라도 실패하면 모두 실패시키는 기능이다. 선생님께서는 다음과 같은 예시를 통해 트랜잭션의 필요성을 말씀해주셨다.

인터넷 쇼핑물 사이트에서 결제가 완료되면 1. 주문 기록을 남긴다. 2. 포인트를 증가시킨다. 3. 구매 기록을 저장한다.

이렇게 세 가지 일을 처리한다. 이 중 하나라도 실패한다면 주문 기록, 포인트, 구매 기록에 있는 데이터가 서로 맞지 않게 되고, 구매 기록이 없어 사용자가 물건을 받지 못하는 등 다양한 문제가 발생할 것이다.

트랜잭션을 적용하면 위와 같은 경우에 하나라도 실패하면 rollback 되기 때문에 앞서 말한 문제가 발생하지는 않을 것이다! 그럼 위 코드에 트랜잭션을 적용해보자!

// UserServiceV2 클래스..
    @Transactional
    public void saveUser(UserCreateRequest request) {
        userRepository.save(new User(request.getName(), request.getAge()));
    }

    @Transactional(readOnly = true)
    public List<UserResponse> getUsers() {
        return userRepository.findAll().stream()
                .map(UserResponse::new)
                .collect(Collectors.toList());
    }

    @Transactional
    public void updateUser(UserUpdateRequest request) {
        User user = userRepository.findById(request.getId())
                .orElseThrow(IllegalArgumentException::new);

        user.updateName(request.getName());
        userRepository.save(user);
    }

    @Transactional
    public void deleteUser(String name) {
        User user = userRepository.findByName(name)
                .orElseThrow(IllegalArgumentException::new);

        userRepository.delete(user);
    }

스프링에서는 이렇게 간단히 트랜잭션을 관리할 수 있도록 @Transactinal 어노테이션을 제공해준다. 이 때 Read 작업만 하는 경우에는 readOnly = true 속성을 줄 수 있다.

 

영속성 컨텍스트란 무엇이고, 트랜잭션과 어떤 관계가 있을까?

영속성 컨텍스트란 테이블과 매핑된 Entity 객체를 관리/보관하는 역할을 수행한다고 한다. 이러한 영속성 컨텍스트는 4가지의 특별한 능력을 가지고 있다. 이번 시간에는 다음 세가지만 배웠다!

  1. 변경 감지

영속성 컨텍스트 안에서 불러와진 Entity는 다음과 같이 명시적으로 save 해주지 않아도 변경을 감지하여 저장할 수 있게 해준다.

    @Transactional
    public void updateUser(UserUpdateRequest request) {
        User user = userRepository.findById(request.getId())
                .orElseThrow(IllegalArgumentException::new);

        user.updateName(request.getName());
        // userRepository.save(user); 생략해도 자동으로 저장이 된다!
    }

 

  1. 쓰기 지연

@Transactional 
public void saveUsers() { 
    userRepository.save(new User("A", 10)); 
    userRepository.save(new User("B", 20)); 
    userRepository.save(new User("C", 30)); 
}

위와 같은 코드가 있을 때 한 줄, 한 줄 DB에 SQL을 날리는 것이 아니라 모아서 한 번에 날리게 된다.

 

  1. 1차 캐싱

@Transactional(readOnly = true) 
public void saveUsers() { 
    userRepository.findById(1L); 
    userRepository.findById(1L); 
    userRepository.findById(1L); 
}

위 코드 역시 DB를 3번 조회해 올 것 같지만, 최초 1번만 DB를 조회하고 나머지 두 번은 영속성 컨텍스트가 보관하고 있는 데이터를 바로 가져와 사용한다!

그리고 이러한 영속성 컨텍스트는 스프링에서 트랜잭션을 사용하면 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다.

 

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


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

2. 객체지향적으로 설계하기 위한 연관관계를 이해하고, 연관관계의 다양한 옵션에 대해 이해한다.

3. JPA에서 연관관계를 매핑하는 방법을 이해하고, 연관관계를 사용해 개발할 때와 사용하지 않고 개발할 때의 차이점을 이해한다.

책 생성, 대출, 반납 API를 만들어보자!

지금까지 배운 내용을 토대로 책 생성, 대출, 반납 API를 만들어보았다. 하지만 우리가 작성한 코드를 조금 더 객체지향적으로 만들 수 있다고 한다! 다음 주에는 기존의 코드를 조금 더 객체지향적으로 만들 수 있는 방법을 알아본다!

 

2주차 과제 정리


각 과제를 블로그로 정리해두어 아래에 링크를 남긴다.

Day 6 : https://www.inflearn.com/blogs/6874

Day 7 : https://www.inflearn.com/blogs/6899

 

2주차를 마치며


  • 아쉬웠던 점

이번주 역시 역시 가장 아쉬운건 일과 학습의 병행이었다. 주말에 미리 이번주 분량의 강의를 모두 들어두었지만 역시 시간이 부족하다...ㅠㅠㅠ

 

  • 칭찬할 점

잠을 줄여가며 과제를 완성하고, 커뮤니티에서 만난 사람들과 함께 과제에 대한 코드 리뷰도 도전해보는 등 최선을 다한 점은 역시 칭찬할 점이다! 일에 지장을 전혀 주지 않았다고는 할 수 없지만... 업무도 크게 무리 없이 진행하였던 나에게 따봉 하나를 선사한다...ㅎㅎ

 

  • 보완할 점

현실적으로 시간이 부족하지 않도록 최선을 다하기란 힘들다는 것을 느꼈다... 마지막 한 주는 그보다는 주어진 시간을 조금 더 알차게 활용하는 것을 목표로 해봐야겠다!

댓글을 작성해보세요.