dew's CSE Studying

[스프링입문]섹션4~섹션6 본문

3-1/[inflearn]스프링입문

[스프링입문]섹션4~섹션6

dew₍ᐢ.ˬ.⑅ᐢ₎ 2024. 10. 5. 15:17

섹션4  회원관리 예제 - 백엔드 개발

1.비즈니스 요구사항 정리

데이터: 회원ID, 이름

기능: 회원등록, 조회

-컨트롤러: 웹 MVC의 컨트롤러 역할

-서비스: 핵심 비즈니스 로직 구현(ex 회원은 중복가입 불가)

-도메인: 비즈니스 도메인 개체(ex 회원, 주문, 쿠폰,... 주로 db에 저장/관리)

-리포지토리: db에 접근, 도메인 개체를 db에 저장/관리

 

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

회원 도메인

package hello.hello_spring.domain;

public class Member {

    private Long id; //id식별자(for system)
    private String name; //이름이 있다

    public Long getId() {
        return id;
    }

    public Member setId(Long id) {
        this.id = id;
        return null;
    }

    public String getName() {
        return name;
    }

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

 

리포지토리

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;

public class MemoryMemberRepository implements MemberRepository{ //option+enter->자동 override가능

    private static Map<Long, Member> store = new 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;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        store.values().stream()
                .filter(member -> member.getName().eqals(name))
                .findAny();
    }

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

 

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

@Test는 순서에 관계 없이 의존관계가 아니도록 설계되어야한다 -> @AfterEach와 clearStore을 사용하여 테스트 하나가 끝날 때마다 clear하도록 설계한다.

package hello.hello_spring.repository;

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

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

class MemoryMemberRepositoryTest { //다른데서 가져다쓸게 아니므로 public일 필요 x

    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();
        assertThat(member).isEqualTo(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);

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

    }
}

4.회원 서비스 개발

package hello.hello_spring.service;

import hello.hello_spring.domain.Member;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.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); //중복 회원 검증

        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        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);
    }

}

5.회원 서비스 테스트

MemberService

package hello.hello_spring.service;
//class name에서 cmd+shift+T 하면 테스트 자동생성됨

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

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

class MemberServiceTest {

    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 = 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);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

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

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

        //then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

 

섹션5  스프링빈과 의존 관계

 

<스프링 빈을 등록하는 2가지 방법>

1)컴포넌트 스캔과 자동 의존관계 설정

2)자바 코드로 직접 스프링 빈 등록하기

1.컴포넌트 스캔과 자동 의존관계 설정

회원컨트롤러가 회원서비스와 회원리포지토리를 이용할 수 있도록 의존관계를 마련해주기

@Controller이 있으면 MemberController 객체를 생성해서 스프링에 넣어둔다+스프링이 관리

*자동 생성자 단축기: cmd+n

*cmd+b : 클래스 이름 선택시 해당 클래스로 이동

 

@Component: 스프링 빈으로 자동 등록됨

@Controller: 외부 요청을 받음

@Service: 비즈니스 로직을 만듦

@Repository: 데이터를 저장

 

controller>MemberController

@Autowired를 생성자에서 쓰면 MemberController가 생성될 때 스프링빈에 등록되어 있는 MemberService 개체를 가져다가 넣어준다(=Dependenct Injection: 의존관계 주입)

service>MemberService

MembeerService(MemberRepository memberRepository)를 보고 memberRepository가 필요한 애구나!를 판단->스프링 컨테이너에 있는 MemberRepository를 넣어준다(이 경우 repository>MemoryMemberRepository)

그럼 요로케 연결 완성~

2.자바 코드로 직접 스프링빈 등록하기

service>SpringConfig 등록

dependenct injection의 세 가지 방법

1)생성자를 통해서(의존관계가 실행중에 동적으로 변하는 경우는 없기 때문에 젤 좋다~)

2)필드 주입: 아예 필드에 @Autowired (별로 안 좋음)

3)setter injection: setter 주입 후 @Autowired(단점: public하게 노출됨->중간에 잘못 바뀌면 문제생김)

 

실무에서는 정형화된 컨트롤러, 서비스, 리포지토리->컴포넌트 스캔 사용

정형화x, 상황에 따라 구현 클래스를 변경해야 하는 경우->설정을 통해 스프링 빈으로 등록

 

섹션6  회원관리 예제

1.회원 웹 기능-홈화면 추가

home.html로 연결되도록 해주었다

 

2.회원 웹 기능-등록

<a href="/members/new">회원 가입</a>

html 코드에 따라 /members/new 생성해주기

controller>MemberController

얘는 딱히 뭐 없이 members/createMemberForm으로 이동한다 (이 경우 template에서 해당 파일을 찾는다)

 

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<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>

그럼 요 html이 뿌려진다. 이때 <form>이 있으므로 값을 입력받는다. 이때 name="name"은 서버에 넘어갈 때 key가 되므로 중요하다!

등록버튼을 눌러주면 "/members/new"에 post방식으로 넘어온다.

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

        memberService.join(member);

        return "redirect:/";
    }

@PostMapping: 데이터를 form 같은 데 넣어서 전달할 때 사용(등록할 때)

@GetMapping: 조회할 때 사용

package hello.hello_spring.controller;

public class MemberForm {
    private String name;

    public String getName() {
        return name;
    }

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

그럼 controller>MemberForm 의 setName을 호출해서 값을 넣어준다. 우리가 꺼낼 땐 getName()으로 꺼내면 된다.

3.회원 웹 기능-조회

    @GetMapping("/members")
    public String list(Model model) {
        List<Member> members = memberService.findMembers();
        model.addAttribute("members", members);
        return "members/memberList";
    }
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<body>

<div class="container">
    <div>
        <table>
            <thead>
            <tr>
                <th>#</th>
                <th>이름</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="member : ${members}"> //${}는 model안에서 가져오는  th:each는 loop 도는 것
                <td th:text="${member.id}"></td>
                <td th:text="${member.name}"></td>
            </tr>
            </tbody>
        </table>
    </div>
</div>
</body>
</html>