spring - 입문2
인프런 김영한님 스프링 입문 강의를 듣고 정리한 내용입니다
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8#
웹 개발 방식
- 정적 컨텐츠
- MVC 와 템플릿 엔진
- API
회원 관리 예제 - 백엔드 개발
컨트롤러 : MVC 의 컨트롤러
서비스 : 핵심 비즈니스 로직 구현(회원 중복 가입 x 등 )
리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
도메인 : 비즈니스 도메인 객체(회원, 주문, 쿠폰 등 데이터베이스에 저장되는 객체)
회원 도메인과 레포지토리 만들기
도메인
spring.hello.spring>domain>Member
package spring.hello.spring.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;
}
//getter setter
}
리포지토리
repository/MemberRepository
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
//NULL 이 리턴됐을 때의 예외를 쉽게 처리하기 위해서 Optional 사용
List<Member> findAll();
}
리포지토리 구현체
repository/MemoryMemberRepository
📌 option + enter로 implement 쉽게 구현
📌 임포트는 option + enter 또는 ctrl + space
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; //0,1,2 키값 생성
@Override
public Member save(Member member) {
member.setId(++sequence); //id 세팅
store.put(member.getId(), member); //스토어 저장
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id)); //optional로 null이어도 감쌀 수 있음
}
@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()); // 전체 value(=Member)들을 리턴
}
}
📌 java
- store.values() :
- Map에 저장된 모든 value들을 Collection 형태로 반환
- .stream()
- store.values()로 얻은 Collection을 스트림으로 변환
- .filter(member -> member.getName().equals(name))
- 람다 표현식으로 member의 getName() 메서드를 호출해서 반환된 값이 name과 같은지를 판단
- true인 요소들만 남기고 그렇지 않은 요소들은 스트림에서 제거해서 필터링
- .findAny()
- 필터링된 스트림에서 임의의 하나의 요소를 찾아 반환
- 반환 타입은
Optional<Member> - 찾은 요소가 있으면 해당 요소를 감싸서 반환하고 없으면
Optional.empty()를 반환
회원 리포지토리 테스트 코드
정상적으로 작동하는지 검증
MemoryMemberRepositoryTest.java
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get(); // Optioanl에서 값을 꺼낼 때는 get()으로
//System.out.println("result =" + (result == member)); 로도 확인 가능
////Assertions.assertEquals(member, result); 로도 확인 가능
// test
// member 와 result 가 같은지 검사
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);
}
}
📌 변수 rename은 ctrl + shift + R 또는 fn + shift + f6으로 한 번에 하기
✅ 전체 코드를 돌려보면 에러가 남 ->
- 테스트 코드 메서드는 임의대로 실행
- findAll() 메서드에서 repository 에 이름이 spring1 인 member1 을 save 했는데, 뒤이어 실행되는 findByName() 메서드에서 이름이 spring1 인 객체를 이용하여 테스트하고 있음
- 서로 다른 테스트 메서드가 사용하는 데이터에서 충돌
- 테스트메서드가 끝나면 사용한 데이터를 초기화 해줘야 함
MemoryMemberRepositoryTest.java
@AfterEach
public void afterEach() {
repository.clearStore();
} //store 초기화
repository/MemoryMemberRepository.java
public void clearStore() {
store.clear();
}
회원 서비스 개발
회원 가입
service/MemberService
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// 회원가입
public Long join(Member member) {
// 같은 이름이 있는 중복 회원 X
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}); // 이 방식 권장
/*
Optional<Member> result = memberRepository.findByName(member.getName());
result.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}); // result가 null이 아닌 다른 값이 있으면 이미 존재하는 회원입니다 뜨게 하기
*/
memberRepository.save(member);
return member.getId();
}
// 전체 회원 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
// 회원 한 명 조회
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
📌 cmd + option + v로 변수 추출 가능
📌 ctrl + t 하고 8번 extract method 하거나 그냥 바로 extract method(cmd + option + m) 하기 -> more option으로 함수명 설정
회원 서비스 테스트
MemberServiceTest.java
📌 cmd + shift + t로 테스트코드 바로 만들기 가능
class MemberServiceTest {
MemberService memberService = new MemberService();
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
@Test
void 회원가입() { //test코드는 한글 가능
//given
Member member = new Member();
member.setName("hello");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
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);
/*
try {
memberService.join(member2);
fail();
} catch (IllegalStateException e){
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}*/
//then
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
✅ IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
- 두 번째 인자인 람다함수 실행 시, 첫 번째 인자값인 IllegalStateException이 발생하는지 확인
📌 ctrl + r로 이전에 실행했던 거 바로 실행
📌 cmd + b 로 navigate
MemberService.java
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
✅ memberRepository를 직접 new로 생성하는 게 아니라 외부에서 넣어주도록 수정
MemberServiceTest.java
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository; //1
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
//생략..
}
각 테스트를 실행하기 전에 MemoryMemberRepository를 만들고 1에다 넣어줌, 걔를 memberService에 넣어줌, 같은 MemoryMemberRepository 사용 -> DI