본문 바로가기
Spring

스프링 가볍게 느껴보자 (1)

by Coarti 2024. 1. 9.

회원가입과 가입된 회원조회를 위한 간단한 사이트를 만들어보자

목적은 3가지로

  • MVC 패턴의 흐름 파악
  • 스프링 빈을 등록하고 교체하는 방법
  • 테스트코드 작성 방법

이것에 대해 확인 할 수 있다.

 

새로운 프로젝트를 만들어 원하는 위치에 압축을 푼다

 

컨트롤러를 관리할 패키지를 만들고 메인에 접속할 HomeController를 만들어 주자

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "home";
    }
}

home.html도 만들자

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

    </div>
</div>

</body>
</html>

중간중간 실행 시켜 잘 되는지 확인도 해보자


회원가입 시 필요한 정보를 담을 도메인을 만들자

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

데이터에 접근하는 Repository를 만들자

유연한 교체를 위해 인터페이스를 생성하자

public interface MemberRepository {
    Member save(Member member);

    Optional<Member> findById(Long id);

    Optional<Member> findByName(String name);

    List<Member> findAll();
}

구현하자

public class MemoryMemberRepository implements MemberRepository{

    private final 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());
    }
    
    // test 코드를 위한 메소드
    public void clearStore() { store.clear(); }
}

데이터베이스는 다음장에서 연결해보고 여기선 메모리를 사용하여 확인해보자


테스트코드를 작성하자

 // org.junit.jupiter.api.Assertions 이 클래스도 테스트 때 사용되는 클래스이다.
import org.assertj.core.api.Assertions;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    // 각 테스트 후 실행 할 동작
    @AfterEach
    public void afterEach() {
        repository.clearStore(); // 메모리 정리
    }

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

        //when
        Member result = repository.save(member);

        //then
        Assertions.assertThat(member).isEqualTo(result);
    }

    @Test
    void findById() {
        // given
        Member member = new Member();
        member.setName("spring1");
        repository.save(member);

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

        // then
        Assertions.assertThat(member).isEqualTo(result);
    }

    @Test
    void findByName() {
        // given
        Member member = new Member();
        member.setName("string");
        repository.save(member);

        // when
        Member result = repository.findByName(member.getName()).get();

        // then
        Assertions.assertThat(member).isEqualTo((result));
    }

    @Test
    void findAll() {
        // given
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

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

        Member member3 = new Member();
        member3.setName("spring3");
        repository.save(member3);

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

        // then
        Assertions.assertThat(result).contains(member1, member2, member3);
    }
}
  • given : 테스트의 필요한 변수, 값을 준비
  • when : 테스트의 대상이 되는 동작 수행
  • then : 테스트의 결과를

 

  • @BeforeEach : 각 테스트 전에 공통적으로 수행되는 내용
  • @AfterEach : 각 테스트 후에 공통적으로 수행되는 내용

기능에 해당하는 Service를 작성하자

public class MemberService {
    private final MemberRepository memberRepository;

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

    // 회원가입
    public Long join(Member member) {
        // 같은 이름의 회원 가입X
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("Already Existence : " + m.getName());
                });
    }

    // 전체 회원 조회
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    // 특정 회원 찾기
    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

테스트 코드를 작성하자

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memoryMemberRepository;

    @BeforeEach
    public void beforeEach () {
        this.memoryMemberRepository = new MemoryMemberRepository();
        this.memberService = new MemberService(memoryMemberRepository);
    }

    @AfterEach
    public void afterEach() {
        memoryMemberRepository.clearStore();
    }
    
    @Test
    void join() {
        // given
        Member member = new Member();
        member.setName("spring");

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

        // then
        Member findMember = memberService.findOne(result).get();
        Assertions.assertThat(findMember).isEqualTo(member);
    }
    
    @Test
    void ExceptionOfJoin(){
        // given
        Member member1 = new Member();
        member1.setName("spring");
        memberService.join(member1);

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

        // when, then
        Assertions.assertThatThrownBy(() -> memberService.join(member2))
                .isInstanceOf(IllegalStateException.class)
                .hasMessageContaining("Already Existence");

    }

    @Test
    void findMembers() {
        // given
        Member member1 = new Member();
        member1.setName("spring1");
        memberService.join(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        memberService.join(member2);

        Member member3 = new Member();
        member3.setName("spring3");
        memberService.join(member3);

        // when
        List<Member> result = memberService.findMembers();

        // then
        Assertions.assertThat(result).contains(member1, member2, member3);
    }

    @Test
    void findOne() {
        // given
        Member member = new Member();
        member.setName("spring");
        Long memberId = memberService.join(member);

        // when
        Member result = memberService.findOne(memberId).get();

        // then
        Assertions.assertThat(result).isEqualTo(member);
    }
}

내부 기능을 구현하였다

이제 컨트롤러를 마무리하여 어플리케이션을 실행시켜보자

@Controller
@RequestMapping("members/")
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping("new")
    public String createForm() {
        return "members/createMemberForm";
    }

    @PostMapping("new")
    public String create(MemberForm form) {
        Member member = new Member();
        member.setName(form.getName());

        memberService.join(member);

        return "redirect:/";
    }

    @GetMapping("")
    public String list(Model model) {
        List<Member> members = memberService.findMembers();
        model.addAttribute("members", members);
        return "members/memerList";
    }
}

create() 메소드에 MemberForm 클래스를 만들어 회원가입 페이지에서 들어오는 정보를 객체로 받아보자

HTML에서 태그에 name 속성의 값과 똑같이 맞춰서 필드를 만들면 스프링이 알아서 값을 넣어준다

public class MemberForm {
    // 아래 회원가입 페이지에서 input 태그의 name 속성의 값이 name 인 것을 볼 수 있다
    private String name;
    
    public String getName() {
        return name;
    }

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

createMemberForm.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="container">
    <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>

memberList.html

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

</body>
</html>

필요한 페이지와 기능을 모두 작성하였다

그렇지만 실행이 되지 않는다 

이유는 스프링에게 Service와 Repository를 등록하지 않았기 때문이다.

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

실행하여 결과를 확인해보자

같은 이름으로 가입했을 경우 에러가 나오는건 정상이다 

맨 위 3가지 목적을 위해 간단히 작업했기에 예외 상황 대처를 해놓은 것이 없다

 


스프링 가볍게 느껴보자 (2)

 

스프링 가볍게 느껴보자 (2)

이번 목표는 2가지다. DB 접근 기술 4가지 코드 수정없이 Repository 교체하는 방법 Service는 그대로 두고 DB에 접근하는 방법만 교체하면서 동일한 결과를 확인하게 된다. 데이터베이스(DB)를 연결해

cloakinghost.tistory.com

 

728x90

'Spring' 카테고리의 다른 글

스프링 가볍게 느껴보자 (3)  (0) 2024.01.11
스프링 가볍게 느껴보자 (2)  (0) 2024.01.10
API  (1) 2024.01.07
정적 컨텐츠, MVC와 템플릿 엔진  (0) 2024.01.07
배포 파일로 만들어 실행  (0) 2024.01.07