스프링 입문 정리

Mind

  • Spring의 모든 것을 외울 수는 없다. 필요한 것을 찾는 능력이 중요하다.
  • 입문 강의는 원리를 이해하기보다는 전체 과정을 훑고 기술의 필요성을 인지하는 단계이다.

Gradle

Spring Initializer 에서 초기 프로젝트를 생성할 때 Maven과 Gradle 옵션이 있다. 강의에서는 Gradle을 사용했는데 Legacy 프로젝트가 아니라면 최근에는 대부분 Gradle을 쓴다고 한다. 강의에서는 프로젝트를 Open 할 때 자동으로 빌드되었고 이후 Gradle 파일을 수정할 때마다 refresh 가 필요했다.

  • Groovy를 기반으로 한 오픈 소스 형태의 자동화 도구

    • Groovy : 자바에 Python, Ruby, Smalltalk 등의 장점을 결합한 동적 객체 지향 프로그래밍 언어
  • 안드로이드 앱 개발환경, JAVA, C/C++, Python 등의 언어로 빌드가 가능하다.

  • Groovy를 사용해서 만든 DSL(Domain Specific Language)을 스크립트 언어로 사용한다.

    • DSL : 웹페이지 영역에 특화되어 사용되는 HTML과 같이 특정한 도메인, 즉 영역이나 용도에 맞게 기능을 구성한 언어
  • 이전에 사용했던 태스크를 재사용하거나 다른 시스템의 태스크를 공유할 수 있는 빌드 캐시 기능을 지원하므로 빌드의 속도를 향상시킬 수 있다.

Controller

  • @Controller를 사용하여 컨트롤러임을 명시해야함

  • @GetMapping("hello") : /hello로 들어오면 해당 메소드 호출

  • return "hello"; : hello.html 로 데이터를 넘겨서 렌더링

  • addAttribute(key, value) : return 에 의해 이동되는 html 파일에 사용될 key를 넘겨 준다. 템플릿에서 key는 Thymeleaf 템플릿 엔진에 의해 value로 치환된다.

  • spring-boot-devtools 라이브러리를 추가하면, html 파일을 컴파일만 해주면 서버 재시작 없이 View 파일 변경이 가능하다. 그렇지 않다면 서버를 재시작해야 변경된다.

Get / Post

  • Get : 어떠한 정보를 가져와서 조회하기 위해서 사용되는 방식

  • Post : 데이터를 서버로 제출하여 추가 또는 수정하기 위해서 사용하는 방식

  • 같은 url에 대해서 Get / Post Mapping이 모두 가능하다.

  • GET과 POST의 비교

img

Logging

현업에서는 System.out.println()으로 출력하지 않고 로그로 출력해서 에러와 로그 파일을 관리한다고 한다. 강의에서는 System.out.println()을 사용할 예정

스프링 부트 로그 관련 표준 라이브러리

  • slf4j (인터페이스)
  • logback (구현체)

웹 개발

  • 정적 컨텐츠 : 서버는 가만히 있고 파일을 웹브라우저에 그대로 내려주는 것

    스프링에서 요청을 처리하는 순서(이는 MVC에서도 똑같이 동작)

    1. 컨트롤러에서 같은 이름이 있는지 찾는다. (templates/home.html)
    2. resources.static에서 찾는다 (static/index.html)
  • MVC와 템플릿 엔진 : html을 서버에서 동적으로 프로그래밍한 후 내려주는 것 ex) JSP, PHP

    MVC 패턴을 쓰는 이유 : 역할과 책임을 명확히 나누기 위해서

    @RequestParm()를 이용하여 Parameter를 넘길 수 있다.

  • API

    • 모바일 클라이언트와 개발을 할 때는 json 포맷으로 클라이언트에 데이터를 전달하면 클라이언트가 알아서 화면을 그림
    • 서버끼리 데이터를 전달할 때 사용
    • 정적 컨텐츠, MVC 방식과 페이지 소스보기를 통해 비교하면 확연한 차이를 알 수 있음

    @ResponseBody 는 viewResolver를 사용하지 않고 return으로 반환되는 객체를 json 형식으로 http body부에 전달

    MappingJackson(spring default)과 구글의 Gson은 객체를 json으로 바꾸는 라이브러리

웹 어플리케이션 계층 구조

img

  • 컨트롤러 : 웹 MVC의 컨트롤러 역할
  • 서비스 : 핵심 비즈니스 로직 구현
  • 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인 : 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨

Test

  • src 폴더 밑에 main과 test 디렉터리가 나뉘어져있다. (요즘에는 그만큼 테스트 코드가 중요하다는 의미)

  • 실무에서는 로직 구현에 걸리는 시간보다 테스트 코드를 작성하는 시간이 더 오래 걸린다고 한다.

  • main 메서드를 실행하거나 웹 어플리케이션의 컨트롤러를 통해서 실행할 때의 단점

    • 준비하고 실행하는데 오래 걸린다
    • 반복 실행하기 어렵다
    • 여러 테스트를 한번에 실행하기 어렵다
  • junit 5 ver. 프레임워크를 사용해서 위의 문제를 해결

  • Assertions

    • Assertions.assertThrows(Exception.class, Logic) : Logic을 실행했을 때Exception.class 예외가 터지기를 기대하며 확인
  • Assertions.assertEquals(Expected, actual) : Expected와 actual이 다르다면 AssertionFailedError 출력

    • Assertions.assertThat(Expected).isEqualTo(actual) : 위와 똑같지만 더 편하다고 한다.
  • TDD란? (같이 스터디하는 punsoo님이 정리를 잘 해주셨다.)

  • 테스트 케이스의 메소드 명은 마음대로 해도 된다. 한글로 쓰기도 한다.

  • 통합 테스트 vs 단위 테스트
    • 통합 테스트 : 편하지만 느리다
    • 단위 테스트 : 불편하지만 빠르다
    • 단위 테스트가 더 좋은 테스트일 확률이 높다
  • given-then-when 문법

    • given : 주어진 상황(data)

    • when : 실행할 것, 검증할 것

    • then : 결과

      @Test
          void 회원가입() {
              //given
              Member member = new Member();
              member.setName("spring");
          
              //when
              Long saveId = memberService.join(member);
          
              //then
              Member findMember = memberService
                  .findOne(saveId).get();
              assertThat(member.getName())
                  .isEqualTo(findMember.getName());
          }
      
  • 어노테이션

    @Test : 메소드마다 run이 가능하도록한다.

    @AfterEach : 같은 클래스 내의 메소드가 끝날때마다 해당 메소드를 Run, 강의에서는 데이터 클리어할 때 사용

    @BeforeEach : 같은 클래스 내의 메소드가 시작하기 전에 해당 메소드를 Run

    @Transactional : 테스트 시작 전에 트랙잭션을 시작하고 테스트 완료 후에 항상 롤백함, DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않음 (AfterEach를 통한 데이터 클리어와 동일한 기능)

    @Commit : @Transactional이 걸려있어도 테스트 돌린 후 결과를 DB에 반영함

    @SpringBootTest(스프링통합테스트) : 스프링 컨테이너와 테스트를 함께 실행함

DI(Depedency Injection)

  • 컴포넌트 스캔

    @Controller : 스프링이 뜰 때 스프링 컨테이너에 스프링 빈을 컨트롤러로 등록해 줌

    @Service : 스프링이 뜰 때 스프링 컨테이너에 스프링 빈을 서비스로 등록해 줌

    @Repository : 스프링이 뜰 때 스프링 컨테이너에 스프링 빈을 리포지토리로 등록해 줌

    @Autowired : 생성자에 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입함, 생성자가 하나라면 생략 가능

    스프링이 관리하는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.

    @Component : 스프링 빈으로 자동 등록됨, main 메소드가 있는 클래스 파일의 패키지 하위에서만 탐색함(설정을 통해 변경가능)

    @Controller, @Service, @Repository는 내부적으로 @Component를 품고있음

    • 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다.(유일하게 하나만 등록해서 공유한다.) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.
  • 직접 스프링 빈 등록하기

    1. 메인파일이 있는 위치에 스프링 빈을 등록할 클래스 파일을 하나 만들고 @Configuration 를 달아준다.

      (강의에서는 SpringConfig 파일이 이에 해당함)

    2. @Bean을 생성자에 달아줘서 스프링 빈에 등록한다

    • 생성자 주입, field 주입, setter 주입 3가지 방법이 있다. 의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다. 생성하는 시점에만 값을 넣고 그 이후에는 변경을 못하도록 할 수 있기때문이다.
  • 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.

  • 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다. (강의에서는 이 경우에 해당함)

    @Bean
    public MemberRepository memberRepository(){
    //        return new MemoryMemberRepository();
    //        return new JdbcMemberRepository(dataSource);
    //        return new JdbcTemplateMemberRepository();
            return new JpaMemberRepository();
        }
    

스프링 DB 접근 기술

  • 서버에 데이터를 저장하면 서버가 꺼지면 데이터가 모두 사라지므로 DB에 데이터를 저장해서 관리한다.

  • H2 Database는 주로 교육용으로 사용하며 가볍고 웹으로 화면도 띄워줘서 편함

  • 순수 JDBC

    • 고대의 기술. 자세한 설명은 생략한다.
  • 스프링 JdbcTemplate

    • Mybatis와 비슷한 라이브러리
    • JDBC API에서 반복 코드를 대부분 제거해주지만 SQL은 직접 작성해야함
    • 디자인 패턴 중에 템플릿 메소드 패턴을 사용함
  • JPA (Java Persistence Api)

    • 기존의 반복 코드는 물론이고 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.

    • SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.

    • 개발생산성을 크게 높일 수 있다.

    • ORM 기술이다.

    • EntityManager로 모든 것이 동작함, 리포지토리에 주입해야함

    • jpql(qlString) : 쿼리를 객체(엔티티)를 대상으로 날리면 sql로 번역함

    • 기본적으로 Hibernate 오픈소스 구현체가 사용됨

    • 어노테이션

      @Entity : JPA가 관리하는 entity가 됨

      @Id : pk로 설정

      @GeneratedValue(strategy = GenerationType.IDENTITY) : id를 자동으로 증가시키면서 생성

      @Column(name = "something") : Column명을 DB의 “something”열과 매핑됨

  • 스프링 데이터 JPA

    • 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있게 해주고 기본 CRUD 기능도 제공함
    • 단순하고 반복적인 코드를 줄여주므로 핵심 비즈니스 로직에 집중 가능
    • 스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술이므로 JPA 학습이 선행되어야함
    • 실무에서는 기본적으로 JPA와 스프링 데이터 JPA를 사용하고 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용함
    • 위의 기술들로도 해결이 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나 JdbcTemplate을 동반하여 사용

AOP(Aspect Oriented Programming)

  • 공통 로직과 핵심 로직을 분리하여 관리한다.
  • 원하는 곳에 공통 관심 사항(로직)을 적용할 수 있게 해주고 개발자가 핵심 비즈니스 로직 개발에 집중할 수 있게 해준다.
  • @Aspect 를 달아주면 AOP class로 사용 가능하다.
  • @Component를 달아주거나 직접 스프링 빈에 등록(이 방법이 선호됨)해야함
  • @Around("execution()") : AOP 적용범위를 정해줌

Java

  • Map 컬렉션

    Map 컬렉션은 key와 value로 구성된 Entry 객체를 저장하는 구조

    여기서 키와 객체는 모두 객체

    Map<Key type, Value Type> map = ~;
    
    • put() : 객체를 추가

    • get() : 키로 객체를 찾아옴

    • remove() : 객체 삭제

    Python의 dict()와 비슷한듯한데 더 심오한 내용을 담고 있어서 추가적인 학습이 필요하다

    강의 예시에서는 HashMap을 사용했지만 실무에서는 동시성 문제를 해결하기 위해 ConcurrentHashMap을 사용한다고 한다

  • 동시성 문제

    • 두 개 이상의 세션이 공통된 자원에 대해 모두 읽고 쓰는 작업(Read, Write)을 하려고 하는 경우 발생할 수 있는 문제
    • 추가적인 내용
  • 스트림

    스트림은 자바 8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.

    List<Sting> 컬렉션에서 요소를 순차적으로 처리하기 위해 Iterator 반복자를 사용할 때

    List<String> list = Arrays.asList("java","python","kotlin");
    Iterator<String> iterator = list.iterator();
    while(iterator.hasNext()){
     String name = iterator.next();
     System.out.println(name);
    }
    

    같은 문제에 Stream을 사용할 때

    List<String> list = Arrays.asList("java","python","kotlin");
    Stream<String> stream = list.stream();
    stream.forEach( name -> System.out.println(name));
    

    위 두가지 방법의 출력이 같다

    • Stream의 특징

    Stream은 Iterator와 비슷한 역할을 하는 반복자이지만 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하므로 병렬 처리가 쉽다는 점 그리고 중간 처리와 최종 처리 작업을 수행하는 점에서 많은 차이를 가지고 있다.

  • java 람다식

    (타입 매개변수, …) -> {실행문; …}

    (타입 매개변수, …)는 {} 를 실행하기 위해 필요한 값을 제공하는 역할

    (int a) -> {System.out.println(a); }
    

    매개변수 타입은 런타임 시 대입되는 값에 따라 자동으로 인식될 수 있기때문에 일반적으로 생략한다

    하나의 매개변수만 있다면 ()를 생략할 수 있다

    하나의 실행문만 있다면 {}를 생략할 수 있다

    a -> System.out.println(a)
    
  • 필터링

    • filter(Predicate) : 조건 필터링, Predicate가 true를 리턴하는 요소만 필터링
    • distinct() : 중복 제거
    • 참고 : 파이썬 filter()
      • filter(function, iterable) : 순회 가능한 데이터 구조(iterable)의 모든 요소에 함수를 적용하고 그 결과가 True인 것들을 filter object로 반환

Errors와 주의할 점

  • 강사가 사용하는 IntelliJ는 유료버젼이고 내가 사용하는 IntelliJ는 커뮤니티 버젼이다. 이 차이로 인해 강의에서 보이는 화면과 내 화면이 다르게 보였는데 오타가 난 줄 알고 헤매며 많은 시간을 소요하게 되었다.

  • H2 콘솔을 켜지 않고 DB를 조작하는 테스트 코드를 Run하면 에러가 발생한다.

  • Preferences에서 Gradle 설정의 Build and run 부분을 Gradle에서 IntelliJ IDEA로 바꾸면 Gradle을 통하지 않고 IntelliJ에서 바로 java파일을 돌리므로 더 빠르게 돈다고 함

References

Written on July 27, 2021