본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[Spring Boot 스터디] 조현상 #2 주차 - 섹션 3

반응형

스프링으로 회원관리예제 다루기!

 

우선 윈도우에서는 맥에서 커맨드 키가 컨트롤 키였고 옵션 키가 Alt 키였다

강의 영상에서 명령 키 얘기를 할 때 윈도우면 위와 같이 키를 누르면 된다.

 

이번 강의는 회원관리 예제를 다루는 시간이었다.

 

간단하게 계층 구조를 본다면

 

컨트롤러  ->  서비스  ->  리포지토리  -> DB 관계이고

컨트롤러, 서비스, 리포지토리는 도메인을 가지고 있다.

 

데베를 접근하고 DB에 저장하고 관리하는 코드들은 리포지토리에서 정의하였고

비지니스 로직들은 서비스에서 관리하는 형태였다.

그리고 도메인은 회원의 정보나 쿠폰 정보 같은 객체들을 생성해서 저장하는 클래스였다.

 

일단 hello.hellospring 디렉토리 안에 domain, repository, service  디렉토리를 만들었다.

 

먼저 domain 디렉토리 안에 Member 라는 클래스를 만들어주었고

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;
    }
}

Member 클래스는 다음과 같이 정의하였다.

name 과 id 멤버 변수가 있고 name, id 값을 변환하고 주는 함수들을 담고 있다.

 

그리고 우리는 아직 DB에 대해서는 다루지 않기 때문에 DB가 하는 역할을 JAVA에 interface를 이용해서 구현을 하였다.

repository 디렉토리 안에 MemberRepository interface를 만들어주었고 

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import java.util.Optional;
import java.util.List;
public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}

다음과 같이 정의하였다.

Member 객체들을 저장해주는 save와

Member 객체들의 id와 name을 가져올 수 있는 findById , findByName 을 만들어주었다

이 때 Optional로 감싸주는 이유는 null 값이 가져와질 수 있기 때문에 오류를 막고자 Optional로 만들어준다고 한다.

 

여기서 alt + Enter 를 하면 쉽게 import를 자동 생성해주기 때문에 유용한 명령어였다.

 

 

그 다음 interface를 만들어주어서 같은 디렉토리 안에 MemberRepository를 상속 받는 MemoryMemberRepository 클래스를 만들어주었다.

 

MemoryMemberRepository에 기능 구현 방법은

 

private static Map<Long, Member> store = new HashMap<>();

를 만들어줘서 Member 객체들을 store 안에 저장하는 방식이었다.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

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

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

코드는 이렇게 구현되어있는데

여기서 중요하게 봐야될 것은 clearStore()의 기능은 앞으로 test를 작성할 때 각각의 test가 끝날 때마다 store 안에 값들을 초기화 시켜주려고 작성한 것이다.

 

솔직하게 findById 와 findByName 구현은 자바 문법을 잘 몰라서 잘 이해가 안됐다.

 

 

repository 기능이 완성이 되었으면 이제 테스트를 해봐야 되는데 여기서 유용한 명령어 키가 있다.

컨트롤 + 쉬프트 + t를 누르면 자동으로 test 케이스를 만들어주는데

test 케이스는 test-> java->hello.hellospring 디렉토리 안에 만들어진다.

 

package hello.hellospring.repository;

import hello.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 java.util.Optional;

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();
        System.out.println(result == member);
        Assertions.assertEquals(member,result);
    }

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

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

        Member result = repository.findByName("spring1").get();
        Assertions.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();
        Assertions.assertEquals(result.size(),2);
    }
}

test할 때 필요한 것은 

import org.junit.jupiter.api.Test; 를 받아서 @Test 를 작성해주면 @Test 안에 함수 한 개만 테스트가 가능하다.

그리고 test 할 때 import org.junit.jupiter.api.Assertions; 를 받아서

Assertions.assertEquals( a, b ) a와 b 비교할 것을 작성해주면 a 와 b가 동일할 경우 run했을 때 초록불이 뜬다.

 

그리고 각각 테스트를 할 때마다 store에 객체를 저장해주기 때문에 store 안에 다른 객체들이 있을 때 꼬일 수가 있는데 

이를 방지하고자

import org.junit.jupiter.api.AfterEach; 를 받고 

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

이렇게 써주면 각 케이스가 끝날 때마다 store을 비워주기 때문에 오류 걱정할 필요가 없어진다.

 

 

 

이제 마지막으로 서비스이다.

service 디렉토리 안에 MemberService 클래스를 만들어주었고

이번 강의 시간에 service로 할 예제는 이름이 중복이면 db(memberRepository 안에 있는 store) 안에 넣어주지 않는 것이고 중복이 아니라면 store 안에 넣어주는 기능을 구현하는 것이었다.

public Long join(Member member){
    //같은 이름이 있는 중복 회원x
    Optional<Member> result = memberRepository.findByName(member.getName());
    result.ifPresent(m -> {
        throw new IllegalStateException("이미 존재하는 회원입니다.");
    });
    memberRepository.save(member);
    return member.getId();
}

그래서 구현을 Optional로 result를 만들어서 findByName을 찾았을 경우는 null 값이 아니니까

result.ifPresent로 null 값이 아닌 경우 IllegalStateException 오류를 발생해서 save가 안되게 만들어주었다.

 

 

service도 repository랑 마찬가지로 test를 작성하면 되는데 여기서 문제가

만약 service도 repository랑 같은 형태로 작성을 하게 된다면 service랑 repository 둘다 서로 다른 store을 가르키게 된다는 것이다.

우리는 하나의 형태로 service 와 repository 둘의 기능을 동시에 확인을 해야되는데 이처럼 작성하게 되면 확인할 수 없다는 점이다.

 

그래서 전과는 다르게 

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

MemberService 클래스 안에는 memberRepository 를 클래스 형태로 만들어줘서 

 

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

test 케이스에다가 beforeEach를 만들어서 test가 시작되기 전에 MemoryMemberRepository 를 만들어서 memeberService로 가서 생성하게 만들어주었는데

 

이 부분도 솔직히 무슨 차이가 있는 지는 잘 모르겠다..

 

스프링을 제대로 공부하려면 자바 문법도 다시 한 번 봐야할 것 같다 ㅠㅠ

 

반응형