스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
본 글은 인프런 김영한 님의 스프링 강의를 기반으로 작성하였습니다.
강의 자료의 출처는 위와 같음을 알립니다.
비즈니스 요구사항 정리
비즈니스 요구사항은 가장 간단한 예제로 진행될 예정이다.
- 데이터 : 회원 ID
- 기능 : 회원 등록, 조회
- 아직 데이터 저장소가 선정되지 않음(가상의 시나리오)
일반적인 웹 애플리케이션 계층 구조
- 컨트롤러 : 웹 MVC의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현
- 지포지토리 : DB에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인 : 비즈니스 도메인 객체 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨
클래스 의존관계
- 아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
- 데이터 저장소로 다양한 저장소(RDB, Nosql)등을 고민중인 상황을 가정
- 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 사용
회원 도메인과 리포지토리 만들기
1. 도메인 패키지를 생성하고 다음과 같은 클래스를 선언한다
id는 시스템에서 구분하기 위해 생성하는 id
name은 뜻 그대로 이름이다
이전 클래스와 똑같이 getter setter를 구성해준다 (alt + insert)
2. 회원 repository 패키지를 만들고 인터페이스를 만든다
package P1.project1spring.repository;
import P1.project1spring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member); // save를 하면 저장소에 저장이 됨
Optional<Member> finById(Long id); // id로 회원을 찾음
Optional<Member> finById(String name); // name으로 회원을 찾음
List<Member> findAll(); //지금까지 저장한 회원을 전부 리스트로 반환해줌
}
참고 : Optional은 findById가 들어올 때 NULL로 반환될 경우 Optional로 묶어서 반환한다고 한다
3. MemoryMemberRepository class를 생성한다
팁
다음과 같이 method들이 오버라이드된다
sequence 는 키 값을 생성해주는 변수라고 생각하면 된다
#여기서 동시성 문제는 생각하지 않고 넘어간다
동시성 문제 -> 하나의 자원에 여러 스레드가 접근해서 데이터 정합성이 깨지는 상태를 말한다.
코드 구현
1. 저장 메서드
public Member save(Member member) { //member 이름은 넘어온 상태
member.setId(++sequence); // id를 sequence로 세팅하고, 메서드가 반복되면 1씩 올라간다
store.put(member.getId(), member); //세팅된 id를 member에 저장한다
return member; //저장된 결과를 반환해준다
}
2. findByid(Long id) 메서드
public Optional<Member> finById(Long id) {
return Optional.ofNullable(store.get(id)); //store에서 get(id)를 한다.
//NULL이 반환될 가능성이 있으면 NULL을 감쌀 수 있는 Optional을 쓴다
}
3. findByName(String name) 메서드
public Optional<Member> finByName(String name) {
return store.values().stream() //자바 식
.filter(member -> member.getName().equals((name))) // member.getName이 파라미터로 넘어온 name하고 같은지 확인
.findAny(); //그 중에서 찾으면 그것을 하나 반환
}
4. findAll() 메서드
public List<Member> findAll() {
return new ArrayList<>(store.values()); //멤버들이 루프를 돌면서 반환된다
}
회원 리포지토리 테스트 케이스 작성
구현한 코드를 테스트할 때 main 메서드를 통해서 실행하거나
컨트롤러를 통헤 실행하면 준비와 시간이 상당히 소요된다.
또한 반복적으로 실행하기 힘들고 여러 테스트를 한 번에 하기 어렵다.
따라서 JAVA는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.
Test하기
test 내에 repository를 생성하고 MemoryMemberRepositoryTest 클래스를 생성한다
팁
save() 테스트
성공 시 아무 결과도 출력되지 않고 코드가 돌아가지만
실패 시
좀더 가독성이 좋은 방법이 있다
팁
findByName() 테스트
위와 같이 "spring1"과 "spring2"를 가입시켜준다
그 후 "spring1"과 member1이 같은지 확인해준다
하지만 member1이 "spring2"인지 체크한다면
아니기 때문에 에러가 발생한다
위 처럼 테스트의 장점은 클래스나 메서드별로 테스트를 돌려 에러를 확인할 수 있다는 점이다.
findAll() 테스트
@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) // List의 사이즈가 2인지
}
findAll()메서드는 위와 같이 테스트를 하면 된다
오류 발생(??) Why?
3개의 메서드를 테스트한 후 다시 전체 클래스를 돌려보면 에러가 발생한다
모든 테스트는 순서와 상관없이 모든 메서드가 각각 동작하도록 설계해야 한다
그래서 findByName() 테스트 케이스를 실행할 때
이전에 저장한 다른 객체가 튀어 나왔다
해결책 : 이전의 테스트 케이스에서 저장했던 데이터를 클리어해야 한다.
@AfterEach는 메서드가 끝날때 마다 어떤 동작을 하도록 기능을 한다.
save() -> afterEach() -> findByName() -> afterEach() ->............
'Spring' 카테고리의 다른 글
[Spring / 스프링 입문] 04. 회원 관리 예제(2) - 백엔드 개발 (0) | 2023.07.07 |
---|---|
[Spring / 스프링 입문] 03. Spring 웹 개발 기초 (0) | 2023.07.03 |
[Spring / 스프링 입문] 02. View 환경 설정 및 빌드 & 실행 (0) | 2023.06.30 |
[Spring / 스프링 입문] 01. 스프링 환경 설정 (0) | 2023.06.27 |