dew's CSE Studying
[스프링입문]섹션4~섹션6 본문
섹션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: 데이터를 저장
@Autowired를 생성자에서 쓰면 MemberController가 생성될 때 스프링빈에 등록되어 있는 MemberService 개체를 가져다가 넣어준다(=Dependenct Injection: 의존관계 주입)
MembeerService(MemberRepository memberRepository)를 보고 memberRepository가 필요한 애구나!를 판단->스프링 컨테이너에 있는 MemberRepository를 넣어준다(이 경우 repository>MemoryMemberRepository)
2.자바 코드로 직접 스프링빈 등록하기
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 생성해주기
얘는 딱히 뭐 없이 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>
'3-1 > [inflearn]스프링입문' 카테고리의 다른 글
스프링부트3 백엔드 개발 6~8장 (4) | 2024.11.16 |
---|---|
스프링부트3 백엔드 개발 3~5장 (1) | 2024.11.09 |
Part 1 스프링 부트 개요 (6) | 2024.11.02 |
[스프링입문] 섹션7 (6) | 2024.10.12 |
[스프링입문]섹션1~섹션3 (3) | 2024.09.27 |