정적인 컨텐츠는 static, 데이터를 동적으로 받아오는 컨텐츠는 templates

 

근데 여기서 

이 html 글자 하나를 수정하고 다시 업데이트하려면 서버를 아예 껐다가 다시 켜야한다(굉장히 소모적임)

그래서 devtools라는 라이브러리를 사용하여서 이런 소모적인 일들을 줄여줄것임.

그 후 build에 해당 html 파일만 리컴파일 시키면 해당 파일이 업데이트가 되는걸 볼 수 있다.

 

h2데이터 베이스를 사용하기에 h2를 깔아주고 폴더의 bin폴더로 들어가서 권한을 주고 h2 터미널에서 데이터 베이스를 실행시킨다

그 후 웹사이트가 하나 실행되는데 그때 앞에있는 주소를 localhost로 변환하여 입력해주면

이렇게 창이 뜨게되는데 이때 처음 서버를 연결할땐 jdbc:h2:~/파일명
이런식으로 처음 연결을 해줘야한다 그래야 파일명으로된 .mv.db폴더가 생기게된다

그 후 첫 연결을 제외한 추후 연결은 

jdbc:h2:tcp://localhost/~/jpashop

이렇게 적어주고 연결을 하면된다

들어가면 이렇게 나오고 여기서 이제 데이터 베이스를 만들면된다.

정적인 컨텐츠는 static, 데이터를 동적으로 받아오는 컨텐츠는 templates

 

근데 여기서 

이 html 글자 하나를 수정하고 다시 업데이트하려면 서버를 아예 껐다가 다시 켜야한다(굉장히 소모적임)

그래서 devtools라는 라이브러리를 사용하여서 이런 소모적인 일들을 줄여줄것임.

그 후 build에 해당 html 파일만 리컴파일 시키면 해당 파일이 업데이트가 되는걸 볼 수 있다.

 

h2데이터 베이스를 사용하기에 h2를 깔아주고 폴더의 bin폴더로 들어가서 권한을 주고 h2 터미널에서 데이터 베이스를 실행시킨다

그 후 웹사이트가 하나 실행되는데 그때 앞에있는 주소를 localhost로 변환하여 입력해주면

이렇게 창이 뜨게되는데 이때 처음 서버를 연결할땐 jdbc:h2:~/파일명
이런식으로 처음 연결을 해줘야한다 그래야 파일명으로된 .mv.db폴더가 생기게된다

그 후 첫 연결을 제외한 추후 연결은 

jdbc:h2:tcp://localhost/~/jpashop

이렇게 적어주고 연결을 하면된다

들어가면 이렇게 나오고 여기서 이제 데이터 베이스를 만들면된다.

어플리케이션 프로퍼티스파일을 지우고 yml파일로 새로 만든후 이렇게 데이터베이스 관련 옵션을 적어준다.

근데 여기서 h2 데이터 베이스에 접근할때 저 MVCC라는 설정이 들어가있는 주소로 들어가면되는데

최신버전에서는 저 부분 옵션이 빠져서 그냥 저걸 제외한 url로 연결 하면됨.

 

@@주의할점

yml파일 작성할때 jpa 부분 줄바꿈안해서 에러남...이유 못찾아서 2시간 버림....

 

기본적인 클래스 생성 후 테스트 코드 작성

기본 세팅은 해줬으니 이제 간단한 기능을 만들어서 테스트 코드를 작성하여 검증해볼것이다.

우선 Member 클래스를 만들어준다.

아까 lombok을 설정해줬기에 getter setter의 코드를 에노테이션으로 확 줄일 수 있다.

 

그 후 기능들을 정의해줄 Member repository 클래스를 만들어준다

여기선 멤버 저장 및 조회를 만들었고 저장시에는 멤버의 id 값을 반환해주고 멤버 조회시에는 id값을 파라미터로 받아서 jpa에서 멤버의 id로 멤버를 찾아서 반환해준다.

 

테스트 코드 작성

 

 

테스트코드를 이런식으로 작성해주었다.

테스트 코드 작성시에 junit으로 작성해주게되는데 여기서 4버전에는 RunWith 에노테이션으로 따로 Spring.runner.class를 해주어야하는데 나는 junit5버전으로 하여서 그부분은 생략하였다.

우선 멤버 객체를 만들어주고 유저이름을 설정해준다.

그 후 멤버 객체를 아까 구현한 멤버 레포지토리의 save기능을 이용하여서 저장해주고 id값을 받아온다(아까 save기능 구현시에 id값을 반환하게 만듬)

그 후 findMember라는 객체에 반환한 id값을 넣어서 find기능으로 멤버를 찾아준다.

그렇다면 이제 저장된 유저와 찾은 유저가 동일한지 판단해야하는데 이건 Assertions.assertEquals(기대값, 실제값)을 통하여 검증해주었다.

저장한 멤버객체와 찾은 멤버객체 각각 id를 추출하는 getId 메서드를 사용하여 id를 검증해주고 마찬가지로 getUsername메서드를 통해 이름 또한 검증해주었다.

 

쿼리 파라미터 로그

쿼리 파라미터 로그를 찍어주기 위해선 이 trace 옵션을 설정해줘야한다 그 후 테스트 케이스를 돌려보면

잘 나오는걸 확인할수있고 다른 라이브러리를 사용하여서 봐도된다

나는 p6spy를 사용하여서 해보겠다.

이렇게 스프링 버전에 맞는 걸 깔아주고 bulid.gradle을 리프레시 시켜준다.

그 후 돌려보면

이렇게 들어가진 데이터가 잘 나오는걸 확인할수있다

하지만 운영 및 배포과정에서는 성능 테스트를 해보고 적용하여야한다.

성능 저하의 원인이 될 수 있기에 성능 테스트 후 > 이정도면 받아들일만하다라고 판단하면 적용.

리전과 가용영역

 

리전:

AWS에서 수많은 컴퓨팅 서비스를 하려면 대규모의 서버 컴퓨터를 모아 둔 곳이 필요할것이다.

이때 한 곳에 전부 다 몰아두면 2가지 문제점이 생긴다.

1. 자연재해가 발생할 경우 모든 서비스 마비

2. 모든 자원이 북미에 있다면, 지구 반대편 아시아 지역은 멀어서 서비스가 느림

 

이 위의 두가지 문제점을 해결하고자 서비스를 하기 위한 자원들을 여러곳에 분산하여 배치해둠.

 

현재 어떤 리전에서 서비스를 하는지 나타내는 이미지

 

가용영역:

가용영역은 리전을 한번 더 분산하여 배치한 것

 

서브넷팅

서브넷팅은 ip주소 낭비를 방지하기 위해 원본 네트워크를 여러개의 서브넷으로 분리하는 과정.

서브넷팅은 서브넷 마스크의 bit수를 증가시키는 것이라고 생각하면된다.

서브넷 마스크의 비트 수를 1씩 증가시키면 할당할 수 있는 네트워크가 2배수로 증가하고 호스트 수는 2배수로 감소한다.

 

192.168.32.0/24는 원래 하나의 네트워크였다. 이때 할당 가능한 host의 수는 2의8승-1=254개 이다. 2를 빼는 이유는 첫번째 주소는 네트워크 어드레스로 사용되고 마지막 주소는 브로드캐스트로 쓰이기때문에 할당할 수 없다.

이때 서브넷 마스크의 비트수를 1 증가시켜서(서브넷팅)192.168.32.0/25로 변경하게 되면 네트워크 id 부분을 나타내는 부분이 24비트에서 24비트로 증가하고 호스트id를 나타내는 부분이 8개 비트에서 7개비트로 줄어든다.

즉 할당 가능한 네트워크 수가 2개로 증가하고 각 네트워크당 할당 가능한 호스트수는 2의7승 -2 = 126개로 줄어든다. 

 

예제

P1) 211.100.10.0/24 네트워크를 각 서브넷당 55개의 Host를 할당할 수 있도록 서브넷팅 한다고 하자.

a) 서브넷 마스크를 구하시오.

풀이 : HostID 비트를 생각하면 쉽다. Host ID를 나타내는 비트가 6개라면 2^6-2=62개의 호스트를 할당할 수 있으므로 충분하다. 그렇다면 32개의 비트 중 26개가 서브넷 마스크의 bit개수이므로 1111111.11111111.11111111.11000000 가 서브넷 마스크가 될 것이다. 즉 255.255.255.192이다.

답 : 255.255.255.192

 

 

203.230.7.0/24

이 아이피 주소에서 찾아낼수 있는 것은?

네트워크 아이디: 203.230.7.0

할당 가능한 호스트 갯수: 256 - 2개

 

 

VPC와 아이피 대역

VPC는 기본적으로 가상의 네트워크 영역이기에 사설 아이피 주소를 가지게됨.

사설 아이피 대역은

10.0.0.0/8

172.16.0.0/12

192.168.0.0/24

이렇게 3개의 대역을 가지며 하나의 VPC에는 네트워크 대역, 혹은 서브넷 대역이 할당 가능함

 

VPC는 실제로 사용시 VPC자체에서도 서브넷을 나누어서 사용하게됨

 

 

예시로 10.0.0.0/16의 아이피 주소를 vpc에 할당한 상황에서, vpc를 원하면 다시 서브넷으로 나눠서(단 vpc를 나눈 서브넷을 다시 나누지는 못함) 각각 서브넷을 원하는 가용영역에 배치하여 사용하게됨

이때 유의할점은 vpc의 서브넷 아이피 대역에서는 실제 네트워크와 달리, 총 5개의 아이피 주소를 호스트에 할당할 수 없다는 것

 

첫주소: 서브넷의 네트워크 대역

두번째: vpc 라우터에 할당

세번째: 아마존이 제공하는 dns에 할당

네번째: 미래를 위해 예약

다섯번째: 브로드 캐스트 주소

 

vpc내부적으로 라우터가 있고, 그렇다면 vpc 내부 서브넷끼리 통신이 되겠다

 

vpc와 외부 통신

기본적으로 사설 아이피 대역은 공용 아이피 대역과 통신이 불가능한데 aws로 인프라를 구축하면 통신이되는 이유는?

여기서 퍼블릭 서브넷과 프라이빗 서브넷이라는 개념이 등장하는데

퍼블릭 서브넷은 vpc서브넷중 외부와 통신이 원활하게 되는 서브넷 대역이고,

프라이빗 서브넷은 외부와 통신이 되지않는 서브넷 대역임

 

 

system call:

운영 체제의 커널이 제공하는 서비스에 대해, 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스이다.

사용자 프로그램이 디스크 파일을 접근하거나 화면에 결과를 출력하는 등의 작업이 필요한 경우, 즉 사용자 프로그램이 특권 명령의 수행을 필요로 하는 경우, 운영체제에게 특권 명령의 대행을 요청하는 것.

 

경우에 따라 시스템 콜이 발생했을 때, 추가적인 정보가 필요할 수도 있는데 그러한 정보가 담긴 매개변수들을 OS 에 어떻게 전달할까?

매개변수를 CPU 레지스터에 전달한다.
- 전달해야 하는 매개변수 보다 레시스터의 수가 작을 수 있다.
매개변수를 메모리에 저장해 해당 메모리의 주소를 레지스터에 전달 할 수 있다.
매개변수는 프로그램에 의해 스택(stack)에 전달(push) 될 수도 있다.
- 2,3 번의 경우 매개변수의 갯수나 길이의 제한이 없기 때문에 선호되는 방식이다.

 

 

시스템 콜 예시

cp in.txt out.txt

먼저 사용자로부터 입력을 받는데 이때 I/O 시스템 콜 호출이 필요하다.
이후 'cp' 프로그램을 실행시키면 먼저 'in.txt' 파일이 현재 디렉토리에서 접근 가능한지를 확인하기 위한 시스템 콜이 호출된다.
이때 접근이 불가능하다면 에러를 발생시킨 후 프로그램이 종료되는데 이때 시스템 콜이 호출된다.
파일이 존재해 접근 가능하다면 복사한 파일을 저장하기 위해 'output.txt' 파일명이 있는지 검사하기 위한 시스템 콜이 호출된다.
이 때도 마찬가지로 이 파일 명이 존재하는지 존재하지 않는지 검사하기 위해 시스템 콜을 통해 확인한다.
만약 파일 명이 이미 존재한다면, 덮어 씌워야 할지 아니면, 이어서 붙여야 하는지 User에게 물어볼 수 있는데 만약 저장하고자 하는 파일 이름이 겹치지 않다면, 파일을 저장해야 하는데 이 때도 시스템 콜을 이용한다.

 

 

프로세스와 스레드

프로세스: 운영체제로부터 할당 받은 작업의 단위

스레드: 프로세스가 할당받은 자원을 이용하는 **실행 흐름의 단위

 

 

OSI 7계층과 TCP/IP 4계층 모델

 

JPA 사용시 SQL과 데이터 중심 설계에서 객체 중심의 설계로 패러다임을 바꿀수있음.

build.gradle에 data-jpa 추가

 

이렇게하면 jpa가 날리는 sql을 볼수가있음

 

jpa를 사용하려면 우선 엔티티를 매핑해야됌

이렇게 멤버 클래스에 엔티티와 아이디 제네레이티드벨류를 추가해줌

 

H2 데이터 베이스

h2 데이터 베이스를 다운받고 bin 폴더에있는 h2.sh에 이런식으로 권한을 주면 됨

 

그러면

이런게 나오게 되는데 test.mv.db가 있는지 확인하고 연결을 눌러야함

그후 만약 이렇게 파일명으로 접근하게되면 애플리케이션이랑 웹콘솔이랑 같이 접근이 안될수 있기에

이렇게 파일에 직접접근하는게 아니라 소켓으로 접근을 하게됨

이렇게 접근을 해줘야함

그 후 이런식으로 멤버 테이블을 만들어줬다.

그럼 이렇게 만들어 진걸 확인할수있고 이걸 조회하려면

이렇게 조회해주면 잘 나옴

이렇게 멤버라는 db에 name이라는 속성에 spring이라는 값을 넣어줄수도있음

저 id 를 선언할때 bigint generated by default as identity라고 선언된걸 볼 수 있는데 이건 
만약 id값이 없으면 여기서 새로 발급한다는 것임.

 

순수 jdbc

애플리케이션에서 db연동을해서 애플리케이션에서 저장하는데 여기서 데이터베이스에 insert 쿼리를 날리고 selcet 쿼리를 날려서 

db에 넣고 빼는것을 할거다.

우선 build.gradle 파일에서

디펜던시를 수정해줄것이다.

이렇게 jdbc와 h2데이터 베이스를 추가해줌

그 후 에플리케이션 프로퍼티에 아까 데이터베이스의 url을 연결해줌

그리고 jdbcMemberRepository를 만들어줌

 

우선 homeController을 만들어서 localhost:8080으로 들어가면 바로 home.html이라는 페이지가 뜨게 만들어줍니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <h1>hello spring</h1>
        <p>회원 기능</p>
        <p>
            <a href="/members/new">회원가입</a>
            <a href="/members">회원 목록</a>
        </p>
    </div>

</body>
</html>

이런식으로 만약 회원가입이나 회원 목록을 누르게된다면 해당 경로로 이동하게 만들어줍니다.

여기서 우선 회원가입 기능부터 추가할것입니다.

 

전에 만들어놨던 멤버 컨트롤러에서 만약 "/members/new"라는 경로로 이동했을시에 

템플릿 폴더 하위에있는 members/createMemberForm이라는 html이 실행되도록 해줍니다.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <form action="/members/new" method="post">
        <div class="form-group">
            <label for="name">이름</label>
            <input type="text" id="name" name="name" placeholder="이름을 입력하세요">
        </div>
        <button type="submit">등록</button>
    </form>
</div>

</body>
</html>

여기서 폼 액션으로 해당 경로에 포스트로 name이라는 벨류로 사용자가 입력한 값을 보내줍니다.

그렇다면 저기서 보낸 것을 post매핑으로 받아와서 setName메서드를 통해 form에 있는 이름을 getName메서드로 넘겨줍니다.

그 후 join메서드를 통해 회원가입을 시켜주고 "/"경로로 리다이렉팅 시켜줍니다.

멤버 폼 클래스

 

이렇게 되면 회원가입기능은 구현이됐고 이제 회원 조회 기능을 구현합니다.

이 조회기능은 그냥 get매핑으로 해당 url에 접근시 finMembers라는 모든 회원을 조회하는 메서드를 통해 리스트로 받아오고 addattribute를 통하여 해당 return에 있는 html파일에 정보들을 넘겨줍니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <table>
        <thead>
        <tr>
            <th>#</th>
            <th>이름</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="member : ${members}">
            <td th:text="${member.id}">
            <td th:text="${member.name}">

        </tbody>
    </table>
</div>

</body>
</html>

 

이렇게되면 제대로 회원 가입 기능 및 조회기능이 나오는걸 볼 수 있습니다.

비즈니스 요구사항

데이터: 회원 id, 이름

기능: 회원 등록, 조회

아직 데이터 저장소가 선정되지 않은 가상의 시나리오

아직 데이터 저장소가 선정되지않아, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계

데이터 저장소는 RDB, NoSQl등 다양한 저장소를 고민중인 상황으로 가정

개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

 

 

우선 이런식으로 멤버 클래스를 작성해주었다 

id와 name의 값을 설정해주고 반환해주는 메서드이고

인터페이스에는 이렇게 회원정보를 저장하고 조회하는 기능을 정의하였다

 

또한 인터페이스를 구현하는 구체적인 클래스인 MemoryMemberRepository를 아래와 같이 정의해주었다.

public class MemoryMemberRepository implements MemberRepository{

    private static Map<Long, Member> store = new HashMap<>();
    //store는 회원정보를 저장하는데 사용되는 hashmap이며 회원의 고유 식별자를 키로 사용하고 해당 회원 정보를 값으로 저장
    private static long sequence = 0L;
    //여기서 이 sequence는 0,1,2 이렇게 키값을 설정해주는것
    @Override
    public Member save(Member member) {
        //멤버 세이브할때 우선 시퀀스값 하나 올려주고
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
        //이렇게하면 store에 넣기전에 멤버 id값을 세팅해주고
        //store에 저장함 그럼 맵에 저장이 되겠지
    }

    @Override
    public Optional<Member> findById(Long id) {
        //여기 findbyid는 store에서 추출해야함
        //근데 만약 추출되는 값이 null일수도 있으니까 Optional.ofNullable로 감싸줘야함
        //이렇게 해주면 클라이언트 측에서 처리 가능
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        //이름을 찾으려면 람다를 써서 그냥 store에서 돌리면됨
        //람다를 사용하여서 받은 name이 member안에있는 name이랑 같은지 확인 후 찾으면 반환
        //여기서는 findAny에 따로 옵셔널 null값 처리를 안해줘도되는 이유가 이 값이 위에보이는 optional로 처리가 됨

            return store.values().stream()
                    .filter(member -> member.getName().equals(name))
                    .findAny();
    }
    @Override
    public List<Member> findAll() {
        //store에 있는 벨류들이 멤버들인데 이게 반환이 됨
        return new ArrayList<>(store.values());
    }
}

 

다음은 구현한 코드를 검증하기 위해 테스트 코드를 작성할 것이다.

 

( 테스트를 진행할때 자바의 main기능을 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해 해당 기능을 실행할수있다.

하지만 이러한 방법으로 하면 실행시 오랜 시간이 걸리고 반복실행이 어려우며 테스트를 한번에 실행하기 어렵다는 단점이 있다.

따라서 JUint을 통하여 테스트를 실행할것이다. )

 

package kimtaeyoung.hellospring.repository;

import kimtaeyoung.hellospring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.List;

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

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    //테스트가 하나씩 끝날때마다 저장소를 다 지움
    //why? 테스트 순서는 무작위로 돌아가기에 다시 써야되는 값들이 미리 들어가있거나 하는 에러를 방지하기위해
    //테스트가 하나씩 종료될때마다 저장소를 지워주는 메서드를 만들어야함 여기서 aftereach가 콜백함수처럼 매번 실행되는 것
    @AfterEach
    public void afterEach() {
        repository.clearStore();
    }

    @Test
    public void save() {
        Member member = new Member();
        member.setName("spring");
        repository.save(member);
        //여기서 값을 꺼낼때 이 findbyid가 옵셔넗이라 뒤에 .get()을 사용하여서 값을 꺼내줌
        //근데 이게 좋은 방법은 아님
       Member result = repository.findById(member.getId()).get();
       //System.out.println("result = " + (result == member));
        //이 어설트이퀄에서 첫번째 매개변수는 내가 기대하는것, 두번째가 실제 결과
        assertEquals(member, result);
    }

    @Test
    public void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);
        //이렇게 해주면 spring 1이라는 회원과 spring2라는 회원이 가입이 된 상태

        //아까와 같이 옵셔넣은 get으로 꺼내주고 assertequal을 이용하여서 기대하는 값과 실제 결과값 비교하기
        // 그래서 member1의 이름이 spring1이니까 여기서 spring1을 찾는 걸해보면 정상 동작됨
        Member result = repository.findByName("spring1").get();
        assertEquals(member1, result);
    }

    @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();

        assertEquals(2,result.size());
    }
}

 

테스트 코드는 이런식으로 작성해주었다.

테스트 코드는 한번에 실행할경우 순서를 가늠하기 어렵기에 따로 storeclear라는 메서드를 만들어주고 그걸 @AfterEach 어노테이션이 부여된 afterEach 메서드를 사용하여서 매번 테스트 후 저장소를 비워주어서 데이터로 인한 에러를 막아줘야한다.

 

기능개발후에 테스트 코드를 작성하여 테스트 하는 방법도 있지만 테스트 코드작성 후에 기능개발을 하는 방법도 있다.

즉, 테스트 코드작성은 중요하다

 

다음은 회원 서비스 클래스를 만들것이다(회원 레포와 도메인을 활용하여 실제 비즈니스 로직을 작성)

 

멤버 서비스 클래스에는 회원가입(이름중복 회원 판별),전체회원 조회, 단일회원조회 기능을 간단하게 넣었다.

public class MemberService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    //회원가입
    public Long join(Member member) {
        //메서드로 빼줌
        validDuplicateMember(member); // 중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    //같은 이름 중복 회원 판별 메서드
    private void validDuplicateMember(Member member) {
        Optional<Member> result = memberRepository.findByName(member.getName());
        //ifpresent는 저 result에 널이 아니라 어떤 값이 있으면 동작하는 것 이게 옵셔넗이기에 가능한것
        //멤버를 그냥 m이라고 한것
        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);
    }
}

 

다음은 아까와 마찬가지로 이 기능들이 잘 동작되는지 테스트 코드를 작성할 것이다.

(꿀팁! 클래스 이름에 커멘드+쉬프트+t를 해주면 바로 테스트 케이스 작성 단축키)

테스트 코드를 작성할때

이런식의 given when then 문법을 사용하면 좋음

given: 무언가가 주어짐

when: 무언가를 실행했을때 

then: 이런 결과가 나와야함

(테스트가 길면 이런식으로 배 가슴 다리 식으로 나누면 보기 좋음)

ackage kimtaeyoung.hellospring.service;

import kimtaeyoung.hellospring.domain.Member;
import kimtaeyoung.hellospring.repository.MemberRepository;
import kimtaeyoung.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

class MemberServiceTest {

    MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();
    //마찬가지로 매번 테스트마다 메모리 초기회

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }
    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    void join() {
        //given
        Member member = new Member();
        member.setName("hello");

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

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertEquals(member.getName(), findMember.getName());
    }

    //테스트 코드 작성시 예외처리 부분도 중요하기에
    //아까 처리한 이름중복이되는지 테스트 코드도 작성
    @Test
    void sameNameJoin() {
        //given
        Member member1 = new Member();
        member1.setName("spring");

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

        //when
        memberService.join(member1);
        //밑에 try catch 문법 대신에 이런 함수를 활용해서 써도됨
        //() -> memberService.join(member2) 이걸 실행했을때 IllegalStateException.class 이게 터져야한다는뜻
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertEquals("이미 존재하는 회원입니다.", e.getMessage());
//        try{
//            memberService.join(member2);
//            fail();
//        } catch (IllegalStateException e) {
//            //발생되는 메세지랑 작성해뒀던 메세지랑 출력값을 비교하여 제대로 되는지 비교
//            assertEquals("이미 존재하는 회원입니다.", e.getMessage());
//        }
        //then

    }
    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

멤버 서비스의 테스트 코드를 작성해주었다.

 

스프링 빈과 의존 관계 설정

- 회원 컨트롤러가 회원 서비스와 회원 리포지토리를 사용할수있게 의존관계 준비

 

멤버 컨트롤러가 멤버 서비스를 통해 회원가입을 시켜야하고 멤버 서비스를 통해 회원 조회를 할 수 있어야함(의존 관계가 있다)

controller에노테이션이 있으면 스프링에서 이 에노테이션이있는 것을 가지고있는데 이것을 스프링 빈이 관리되고있다라고함

 

이렇게 스프링 컨테이너에 hellocontroller가 있는데 컨트롤러 에노테이션이있으면 스프링에 딱떠서 스프링에서 관리를 해줌

 

스프링 프로젝트 생성시 3.2.3 , 자바 17이상, 디펜던시는 web으로 할거기에 spring web, 템플릿 엔진은 thymeleaf

 

스프링 부트 라이브러리

 

spring-boot-starter-web에는 

spring-boot-starter-tomcat: 톰캣 (웹서버)

spring-webmvc: 스프링 웹 mvc가 들어가있음

 

spring-boot-starter-thymeleaf : 타임리프 템플릿 엔진

spring-boot-starter: 스프링부트 + 스프링 코어 + 로깅이 다 땡겨져옴

 

테스트 라이브러리

spring-boot-starter-test

요즘은 junit5를 많이씀

 

스프링 부트 동작 환경

 

이게 hellocontroller인데 여기서 return hello를 하게되면

여기 resources밑에 템플릿에서 저 hello라는 것을 찾아서 렌더링을 하게됨

컨트롤러에서 리턴값으로 문자열을 전달하면 viewresolver가 화면을 찾아서 처리해줌

 

그럼 이제 전에 만들어놨던 hello.html을 보게되면

이렇게 되어있는데 

저기서 data라는 것은 전에 있던 hellocontroller에 있는 model의 키값이라 저기서 데이터를 받아와서 아까 봤던 hello!!가 출력됨.

 

그리고 폴더중에 static이라는 정적 파일이있는데 이 하위 파일로 index.html이나 이런것들은 경로가 

localhost:8080/index.html이런식으로 들어가면 저 파일안에있는 html이 나오게됨.

 

api

 

api방식은 mvc방식이랑 다르게 데이터를 그대로 내려주는 방식임

 

이렇게 보면 스프링에서 서버를 키고 로컬로 들어가서 hello-mvc라는 url로 들어가게되면 밑에 return hello-template에 따라서 리소스에서 저 html파일을 따라가서 거기서 주어진 data를 html과 같이 화면에 띄우는것이라면

밑에있는 api 방식은 그냥 그딴거 필요없고 데이터를 바로 표출해줌

아마 클라이언트 측에서 이런식으로 api에서 데이터를 요청해서 받아서 데이터를 받아오는듯,,, 확실한건 잘 모름

 

이런 api방식으로 하고 해당 주소로 들어가게된다면

평소에 클라이언트측에서 데이터를 받는 정보가 오게된다!!(json방식)

그냥 키: 벨류 로 이루어진 구조임

 

리스폰스 바디 사용 원리

 

 

+ Recent posts