[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

비즈니스 요구사항 정리

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

회원 도메인과 리포지토리 만들기

//Member.java
package hello.hellospring.domain;

public class Member {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String Name);
    List<Member> findAll();
}
  • null을 반환할 수 있음. 이 때 Optional로 감싸서 반환하는 방법 많이 사용
//MemoryMemberRepository.java

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;


public class MemoryMemberRepository implements MemberRepository{

    @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));
    } // null을 반환할 수 있기 때문에 Optional.ofNullable()로 감싸줌

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream().filter(member -> member.getName().equals(name))
                .findAny();
    } //parameter로 넘어온 name과 같은게 있는지 확인, 찾으면 반환, 아니면 null

    @Override
    public List<Member> findAll() {
        return new ArrayList<>((store.values()));
    } //store에 있는 member들 반환
}
  • option + enter 단축키 -> 필요한 메소드 자동 생성
  • 실무에서 List 많이 사용

 

 

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

  • 회원 리포지토리 클래스가 제대로 작동하는지 확인하기 위한 코드
  • main이나 컨트롤러를 통한 실행은 실행하는데 오래 걸리고 반복실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있음  -> JUnit이라는 프레임워크로 테스트를 실행해서 문제 해결
  • src > test>java>hello.hellospring >repository 에 파일 생성
//MemoryMemberRepositoryTest.java

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;


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(); //Optional에서 값을 꺼낼 때 get() 사용 좋은 방법은 아니지만 테스트코드에서는 사용 O
       // System.out.println("result =" +(result ==member));
       //Assertions.assertThat(member).isEqualTo(member); //실행했을 때 초록불 뜨면 정상 동작
        assertThat(member).isEqualTo(member);
    }

    @Test
    public void findByName(){
        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();
        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();

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

}

 

//MemoryMemberRepository.java

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

	...

    public void clearStore(){
        store.clear();
    }
}

 

  • 전체 테스트를 돌리려면 class를 실행
  • 모든 테스트는 순서와 상관없이 동작이 되도록 설계해야함 -> 테스트가 끝날 때마다 repository를 지워주는 코드를 넣어야함
    • @afterEach : 각 테스트가 종료될 때 마다 실행됨
  • 테스트를 먼저 만들고 구현 클래스를 만들어 개발하는 것 -> 테스트주도 개발 (TDD)

 

회원 서비스 개발

//MemberService.java

package hello.hellospring.service;

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

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

public class MemberService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();


    /*회원가입*/

        public Long join(Member member){
            //같은 이름 중복X
            validateDuplicateMember(member);
            return member.getId();
        }

        /*중복 회원 검증 함수*/
        private void validateDuplicateMember (Member member){
            /*Optional<Member> result = memberRepository.findByName(member.getName());
            result.ifPresent(m -> {
            throw new IllegalStateException("이미 존재하는 회원입니다.");   Optional을 바로 반환하는 것 권장 X*/
            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);
        }

    }
  • command+option+v  단축키 : 반환값 받는 변수 자동 생성
  • 로직은 method로 뽑아내는 것이 좋음. control+t -> Extract Method를 통해 method로 분리

 

 

회원 서비스 테스트

//MemberServiceTest.java

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;


import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {
   /* MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository(); MemberService에서 생성한 repository와 다른 repository가 생성됨*/

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService=new MemberService(memberRepository);

    }



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

    @Test
    void 회원가입() {

//Given
        Member member = new Member();
        member.setName("hello");
//When
        Long saveId = memberService.join(member);
//Then
        Member findMember = memberRepository.findById(saveId).get();
        assertEquals(member.getName(), findMember.getName());

    }
    @Test
    public void 중복_회원_예외() throws Exception {
//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){
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

        }*/
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> memberService.join(member2));//예외가 발생해야 한다.
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

    }


        @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}
//MemberService.java

package hello.hellospring.service;

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

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

public class MemberService {

    private final MemberRepository memberRepository;

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

		. . .

    }
  • command + shift + T 단축키 : test 더 간편하게 생성할 수 있는 단축키
  • test 코드는 한글로 적어도 괜찮음. 직관적으로 알아보기 쉬워 현업에서도 자주 사용
  • given-when-then 문법 : 무언가가 주어지고 (given)  실행 했을 때 (when)  결과가 이거여야한다(then)
    • 테스트코드가 길어졌을 때 확인하기 편해짐
  • 외부에서 repository를 넣어줌 : Defendency Injection(DI)
    • @BeforeEach : 각 테스트 실행 전에 호출된다. 테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고, 의존관계도 새로 맺어준다.

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

정적 컨텐츠

src > resources > static에 파일을 작성한 후  "localhost:8080/ 작성한 파일"로 이동하면 입력한 파일이 그대로 반환됨

  1. localhost:8080/hello-static.html 검색
  2. 내장 톰캣 서버가 스프링에 hello-static.html이라는 요청이 왔다고 스프링에 전달
  3. 스프링부트가 controller에 hello-static.html이 있는지 확인 (controller가 우선권을 가짐)
  4. 없으면 resources안에 static/hello-static.html을 찾아 웹브라우저에 반환
  • 원하는 파일을 static에 만들면 만든 정적파일이 그대로 반환됨
  • 어떠한 프로그래밍을 할 수 없음

MVC와 템플릿 엔진

MVC : Model, View, Controller

  • 과거에는 view, controller 분리 X -> 역할을 분리할 필요 O, view는 화면을 그리는데 집중, controller는 내부적 로직을 처리하는데 집중

 

//src/main/java/hello.helloSpring/controller/HelloController.java

public class HelloController {
      @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>
  • localhost:8080/hello-mvc 접속시 "Required String parameter 'name' is not present" 오류 -> command+p 단축키 이용하여 parameter 정보 보기
  • prameter required의 default 값이 true이기 때문에 기본으로 값을 넘겨주어야함 -> ex) localhost:8080/hello-mvc?name =spring
  1. localhost:8080/hello-mvc 검색
  2. 내장 톰캣 서버가 스프링에 hello-mvc 라는 요청이 왔다고 스프링에 전달
  3. 스프링부트가 controller에 hello-mvc 있는지 확인
  4. 있으므로 해당 method 호출. (return hello-template , model(name:spring) )을 스프링에 넘겨줌
  5. viewResolver (view를 찾아주고 템플릿 엔진 연결)가 템플릿 엔진에 처리해달라고 넘김
  6. 템플릿 엔진이 변환 후 웹 브라우저에 넘겨줌

API

@Controller
  public class HelloController {
      @GetMapping("hello-string")
      @ResponseBody
      public String helloString(@RequestParam("name") String name) {
          return "hello " + name;
      }
}
  • @responseBody :

    뷰 리졸버( viewResolver )를 사용하지 않음

    http body에  return 값 직접 반환.
@Controller
  public class HelloController {
      @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;
} }
}
  • return 값으로 객체 넘김 (json 방식 - {key:value} )
  • 단축키 command+N -> getter, setter 생성

@responseBody 사용 원리

  1. localhost:8080/hello-api 검색
  2. 내장 톰캣 서버가 스프링에 hello-api 라는 요청이 왔다고 스프링에 전달
  3. 스프링부트가 controller에 hello-api 있는지 확인
  4.  있는거 확인, @responseBody가 붙어있으므로 viewResolver 동작 X, HttpMessageConverter 동작 O
  5. 기본 문자면 StringHttpMessageConverter (그대로 넘겨줌), 기본 객체면 MappingJackson2HttpMessageConverter( json 방식으로 데이터를 변환하여 넘겨줌)
  6. byte 처리 등등 기타 여러 HttpMessageConverter 존재

이번에 GDSC에서 스프링 입문 스터디를 진행하게 됐습니다😄

스터디는 유명한 김영한님의 로드맵을 따르기로 했습니다.

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

강의에서 알려주는대로 JAVA 11과 IntelliJ을 설치하고(JAVA 11 설치 시 오류 발생합니다... 자세한 내용은 하단에)

 

 

Download the Latest Java LTS Free

Subscribe to Java SE and get the most comprehensive Java support available, with 24/7 global access to the experts.

www.oracle.com

 

 

IntelliJ IDEA 다운로드: 우수성과 인체 공학이 담긴 JetBrains Java IDE

 

www.jetbrains.com

https://start.spring.io/ 에 들어가 아래 사진과 같이 설정을 해줍니다.

하단에 GENERATE 버튼을 눌러 파일을 다운 받은 후 압축을 풀고 intelliJ에서 build.gradle 파일을 열어줍니다.

그런데 파일을 열어보니 김영한님의 강의와 다르게 build.gadle 파일에 sourceCompatibility가 '11'이 아닌 '17'로 뜨고

HelloSpringApplication을 실행시켰을 때 아래와 같이

Doesn't say anything about its target Java version (required compatibility with Java 11)

 어쩌구 저쩌구.. 하는 에러가 떴습니다....

이유를 찾아보니 스프링 부트 3.0부터 JAVA 17이 필수라고 하더라구요!

 

JAVA를 17로 업그레이드 하는건 뭔가.. 복잡해보여서 삭제하고 다시 설치하는 방법을 선택습니다.

 

JAVA 제거 후 재설치 하기

1. 터미널에 아래 코드를 입력합니다.

cd /Library/Java/JavaVirtualMachines/

2. 'ls' 를 입력해 설치되어 있는 jdk 파일을 확인해줍니다.

(현재 저는 17버전으로 재설치한 상태이기 때문에 17로 뜹니다.)

 

3. 아래 코드를 입력해 삭제해줍니다.

sudo rm -rf 상단에서 확인한 jdk 파일명

제 파일로 예를 들면 

sudo rm -rf jdk-17.jdk

위에 코드를 입력하면 비밀번호를 입력하라고 나오는데 맥북에서 설정한 비밀번호 입력해주면 됩니다.

 

4. 자바  설치 링크로 돌아가 17버전을 다운로드 받아줍니다.

 

 

 

위와 같은 과정을 통해 정상적으로 HelloSpringApplication을 실행시킬 수 있었습니다.

 

 

 

 

 

 

 

+ Recent posts