SpringBoot 강의정리

고 연우·2022년 9월 18일
0

SpringBoot

목록 보기
1/8

1. 스프링 부트 스타터 사이트로 스프링 프로젝트 생성

  • https://start.spring.io 에서 스프링부트 프로젝트 생성 가능
  • project : Gradle
  • Language : Java
  • Spring Boot : 2.7.3
  • project Metadata
    • Group : hello
    • Artifact : hello-spring
    • Dependencies : Spring Web, Thymeleaf

스프링부트 프로젝트

  • main함수 실행 -> Tomcat started on port(s): 8080 (http) with context path '' 참고
  • 웹 페이지에서 localhost:8080 입력

2. 스프링부트 라이브러리 살펴보기

  • VSCode에선 왼쪽 칸에 'Gradle' 이란 코끼리 모양 클릭
  • System.out.println 말고 log 형식으로 남기도록..
  • Gradle : 의존관계가 있는 라이브러리를 함께 다운로드함

스프링부트 라이브러리

  • spring-boot-starter-web
    • spring-boot-starter-tomcat : 톰캣(웹서버)
    • spring-webmvc : 스프링 웹 MVC
  • spring-boot-starter-thymeleaf : 타임리프 템플릿 엔진(View)
  • spring-boot-starter(공통) : 스프링 부트 + 스프링 코어 + 로깅
    • spring-boot
      • spring-core
    • spring-boot-starter-logging
      • logback, slf4j

테스트 라이브러리

  • spring-boot-starter-test
    • junit : 테스트 프레임워크( junit5 를 자주 씀 )
    • mockito : 목 라이브러리
    • assertj : 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
    • spring-test : 스프링 통합 테스트 지원

3. View 환경 설정

Welcom page 만들기

  • static/index.html 을 올려두면 스프링 부트가 제공하는 Welcome page 기능을 제공
    • src - main - resources - static - 파일 생성( index.html )
    • 스프링 부트는 index.html 파일을 먼저 찾음
  • 아래 내용 작성
<!DOCTYPE HTML>
<html>
    <head>
        <title>Hello</title>
        <meta http-equiv = "Content-Type" content="test/html"; charset=UTF-8" />
    </head>
<body>
    Hello
    <a href = "/hello">hello</a>
</body>
</html><!DOCTYPE HTML>
<html>
    <head>
        <title>Hello</title>
        <meta http-equiv = "Content-Type" content="test/html"; charset=UTF-8" />
    </head>
<body>
    Hello
    <a href = "/hello">hello</a>
</body>
</html>

thymeleaf 템플릿 엔진

package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {
    
    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello!!");
        return "hello";
    }
}
  • Model
  • @Controller
  • @GetMapping
  • resources - templates - hello.html 생성
<!DOCTYPE HTML>
<html xmlns=th="http://www.thymeleaf.org">
<html>
    <head>
        <title>Hello</title>
        <meta http-equiv = "Content-Type" content="text/html; charset=UTF-8" />
    </head>
<body>
    <p th:text="'안녕하세요. ' + ${data}" >안녕하세요. 손님</p>
</body>
</html>
  • 컨트롤러에서 리턴 값으로 문자를 반환하면 viewResolver 가 화면을 찾아서 처리함
    • 스프링 부트 템플릿엔진 기본 viewName 매핑
    • 'resource:templates/' + {Viewname} + .'html'

4. 빌드하고 실행하기

window에서 cmd창으로 springBoot 빌드

  • cmd창 오픈 -> cd (gradle.bat 파일 있는 위치로 이동)
  • 빌드 : gradlew build 명령어 통해 빌드됨
    • build 했던 폴더 삭제 : gradlew clean
    • build 했던 폴더 삭제하고 다시 빌드 : gradlew clean build
  • 실행 : java -jar (프로젝트명)-0.0.1-SNAPSHOT.jar 명령어 통해 실행됨
  • 실행 종료는 Ctrl + C 입력

5. 정적 컨텐츠

  • 파일을 그대로 웹브라우저에 보여줌
  • 스프링 부트는 정적 컨텐츠 지원함
  • resource - static 에 'hello-static.html' 파일 생성
<!DOCTYPE HTML>
<html>
    <head>
        <title>static content</title>
        <meta http-equiv = "Content-Type" content="text/html; charset=UTF-8" />
    </head>
<body>
    정적 컨텐츠 입니다.
</body>
</html>

6. MVC와 템플릿 엔진

  • MVC : Model, View, Controller
  • controller -> HelloController.java 에 아래 내용 추가
import org.springframework.web.bind.annotation.RequestParam;

@GetMapping("hello-mvc")
    public String helloMvc(@RequestParam("name") String name, Model model) {

        model.addAttribute("name", name);
        return "hello-template";

    }
  • resources -> templates -> hello-template.html 생성
<html xmlns=th="http://www.thymeleaf.org">
<body>
    <p th:text="'hello. ' + ${name}" >hello! empty</p>
</body>
</html>

7. API

@ResponseBody

  • @ResponseBody 사용하면 viewResolver 사용 안함
    • viewResolver 대신 HttpMessageConverter 동작
    • 기본 문자처리 : StringHttpMessageConverter
    • 기본 객체처리 : MappingJackson2HttpMessageConverter
    • byte 처리 등등 기타 여러 HttpMessageConverter 가 기본으로 등록되어 있음
  • 대신 HTTP의 BODY에 문자 내용을 직접 반환( HTML BODY TAG를 말하는 것 아님 )

예제 1

  • controller -> HelloController.java 에 아래 내용 추가
import org.springframework.web.bind.annotation.ResponseBody;

@GetMapping("hello-string")
    @ResponseBody
    public String helloString(@RequestParam("name") String name) {

        return "hello " + name; // @ResponseBody : http의 body 에 직접 내용을 넣어줌
    }
  • @ResponseBody
  • @RequestParam

예제 2

  • controller -> HelloController.java 에 아래 내용 추가
@GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {

        Hello hello = new Hello();
        hello.setName(name);

        return hello;
    }

    static class Hello {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

8. 회원 관리 예제 - 백엔드 개발

비즈니스 요구사항 정리

  • 데이터 : 회원 ID, 이름
  • 기능 : 회원 등록, 조회
  • 아직 데이터 저장소가 선정되지 않음( 가상의 시나리오 )

일반적인 웹 애플리케이션 계층 구조

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

클래스 의존관계

  • 아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
  • 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정
  • 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

회원 도메인, 레포지토리

도메인

  • hellospring -> domain 폴더 생성( pcakage 생성 )
  • domain 폴더 안에 Member.java 생성
package hello.hellospring.domain;

public class Member {
    
    private Long id;                // 시스템이 저장하는 id
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
}

레포지토리

  • hellospring -> repository 폴더 생성( pcakage 생성 )
  • repository 폴더 안에 MemberRepository.java 생성 -> interface로 선언
package hello.hellospring.repository;

import java.util.List;
import java.util.Optional;

import hello.hellospring.domain.Member;

public interface MemberRepository {
    
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
    
}
  • Optional : Integer나 Double 클래스처럼 'T'타입의 객체를 포장해 주는 래퍼 클래스(Wrapper class)
    • 복잡한 조건문 없이도 널(null) 값으로 인해 발생하는 예외를 처리할 수 있음
  • interface
  • repository폴더 안에 MemoryMemberRepository.java 생성 -> MemberRepository.java를 implements 함
package hello.hellospring.repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import hello.hellospring.domain.Member;

public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {

        member.setId(++sequence);
        store.put(member.getId(), member);

        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {

        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {

        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        
        return new ArrayList<>(store.values());
    }

}
  • Map
  • ArrayList
  • implements

회원 리포지토리 테스트 케이스 작성

  • 자바는 JUnit 이라는 프레임워크로 테스트 실행

회원 리포지토리 테스트 코드 작성

  • src -> test -> java -> hello -> hellospring 안에 repository 패키지(폴더) 생성
  • repository 패키지 안에 MemoryMemberRepositoryTest.java 생성
package hello.hellospring.repository;

import java.util.List;

import org.assertj.core.api.Assertions; //assertThat
import org.junit.jupiter.api.AfterEach;
//import org.junit.jupiter.api.Assertions;          //assertEquals
import org.junit.jupiter.api.Test;

import hello.hellospring.domain.Member;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach() {

        repository.clearStore();        // 테스트 하나씩 실행되고 나서 동작
    }

    @Test
    public void save() {

        Member member = new Member();
        member.setName("spring");

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

        // Assertions.assertEquals(result, member);
        Assertions.assertThat(member).isEqualTo(result);

        // System.out.println("result = " + (result == member));
    }

    @Test
    public void fineByName() {

        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();

        Assertions.assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll() {

        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        Assertions.assertThat(result.size()).isEqualTo(2);
    }

}
  • MemoryMemberRepository.java 추가
public void clearStore() {

        store.clear();
    }

회원 서비스 개발

서비스 로직

  • main -> jave/hello/hellospring -> service 패키지(폴더) 생성
  • MemberService.java 생성
package hello.hellospring.service;

import java.util.Optional;
import java.util.List;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

public class MemberService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    /**
     * 회원가입
     * 
     * @param member
     * @return
     */
    public Long join(Member member) {

        validateDuplicateMember(member); // 중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {

        // 같은 이름이 있는 중복 회원 X

        // Optional<Member> result = memberRepository.findByName(member.getName());
        // result.ifPresent(m -> {
        // throw new IllegalStateException("이미 존재하는 회원입니다.");
        // });

        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers() {

        return memberRepository.findAll();
    }

    /**
     * 회원 조회
     */
    public Optional<Member> findOne(Long memberId) {

        return memberRepository.findById(memberId);
    }
}

회원 서비스 테스트 코드

  • src -> test -> java -> hello -> hellospring 안에 service 패키지(폴더) 생성
  • service 패키지 안에 MemberServiceTest.java 생성
package hello.hellospring.service;

import static org.junit.jupiter.api.Assertions.fail;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
//import org.junit.jupiter.api.Assertions;        //assertThrows
import org.junit.jupiter.api.Test;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {

        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    public void 회원가입() {

        // given
        Member member = new Member();
        member.setName("hello");

        // when
        Long saveId = memberService.join(member);

        // then
        Member findMember = memberService.findOne(saveId).get();
        Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());

    }

    @Test
    public void 중복_회원_예외() {

        // given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);

        // 1
        try {
            memberService.join(member2);
            fail();
        } catch (IllegalStateException e) {
            Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }

        // 2
        // Assertions.assertThrows(IllegalStateException.class, () ->
        // memberService.join(member2));

        // 3
        // IllegalStateException e =
        // Assertions.assertThrows(IllegalStateException.class, () ->
        // memberService.join(member2));

        // Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        // then
    }

    @Test
    public void findMembers() {

    }

    @Test
    public void findOne() {

    }
}
  • MemberService.java 추가
private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

0개의 댓글