#김영한 #스프링 #Spring #인프런 #인프런수업
본 포스팅은김영한
선생님의스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB접근 기술
강의를 기반으로 작성되었습니다.
비지니스 요구사항 정리
데이터 : 회원ID, 이름
기능 : 회원 등록, 조회
아직 데이터 저장소가 선정되지 않음(시나리오)
회원 도메인과 회원 레포지토리 만들기
- 도메인 패키지 생성 후 클래스 생성
패키지 위치 : src/main/java/hello.hellospring/domain
클래스 위치/클래스명 : /domain/Member package hello.hellospring.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; } }
2. 회원 레포지터리 생성 후 클래스별 소스작성
- 패키지 위치 : src/main/java/hello.hellospring
- 패키지 명 : 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);
List<Member> findAll();
}
- 동일 패키지에 구현받을 클래스 작성
- 클래스명 : MemoryMemberRespository
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRespository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
// sequence 는 번호를 부여해주는 객체라고 보면 됨
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 CASE 작성하기
내가 원하는 데로 동작을 할건지 테스트 코드로 검증한다
개발한 기능을 실행하여 테스트 할때 자바의 Main 메서드를 통해 실행 하거나
웹 애플리케이션의 컨트롤러를 통해 해당 기능을 실행한다
이러한 방법은 준비하고 실행하는데 시간이 오래걸리고 반복 실행하기 어렵기 때문에
여러 테스트를 실행하기 어렵다는 단점이 있다
자바는 JUNIT 프레임워크로 테스트를 실행하여 문제를 해결할 수 있다
테스트 코드를 작성 할 때엔 해당 메서드 위에 어노테이션 명시가 필요하다
@Teat : 테스트코드라는것을 명시
@AfterEach : 메소드 테스트 후 처리로직
테스트 코드를 돌린 뒤에 항상 비워줘야한다
TEST class
@AfterEach
public void afterEach(){
repository.clearStore();
}
해당 메서드를 본문 코드에 선언해놓는다
본문 코드
public void clearStore(){
store.clear();
}
여기서 store는 본문 소스코드에 멤버변수로 HashMap 자료형 변수이다
assertThat이 나는 되지 않아서 찾아보니
import가 안되어서 그랬다
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
서비스패키지와 회원서비스 클래스 만들기
회원 리포지토리랑 도메인을 활용하여 작성한다
용도에 따른 네이밍 짓는 법과 테스트코드 작성 및 리팩터링도 진행해보자
- 패키지 위치 : src/main/java/hello.hellospring
- 패키지 명 : service
- 인터페이스 명 : MemberService
- 소스
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRespository;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRespository();
public long Join(Member member){
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);
}
해당 소스는 리팩토링 까지 진행한 소스이다
리팩토링된 결과
// 회원 저장
public long Join(Member member){
validateDuplicateMember(member); // 중복 회원 검증
memberRepository.save(member);
return member.getId();
}
// 중복 회원 검증 처리후 반환
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m->{
throw new IllegalStateException("이미 존재하는 회원입니다");
});
}
처음 소스
public long Join(Member member){
// 같은 이름이 있는지 중복회원 가입 불가
Optional<Member> result = memberRepository.findByName(member.getName());
result.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다");
});
memberRepository.save(member);
return member.getId();}
findByName(member.getName())
리턴값은 확인시 optional로 반환된다
Optional로 객체를 진행하기 때문에 값이 있는 경우 바로 result.ifPresent()
를 진행할 수 있다.
꺼낼때는 get()
다만 바로 꺼내는건 주로 사용하지 않고 is등 여러 메서드를 사용하길 권장한다
✋ 잠깐! ✋
Optional이란건 뭘까요 ?
Java8에서 도입된 컨테이너 클래스이다
변수가 'null’일 가능성이 있는 객체를 감싸는 wrapper이다
이 객체는 어떤 값이 존재 할 수 있고 존재 하지 않을 수 있는 상황(nullpoint예외)을 더 안전하게 다루기 위해 사용된다
보통 if(값이 있는경우, 데이터가있는경우)문을 간편하게 사용한다 보면된다
주요매서드
- empty() : 비어있는 Optional 객체를 반환
- Optional<String> empty = Optional.empty();
Optional<String> opt = Optional.of("value");
- of(T value) : null이 아닌 값을 가지는 Optional객체 반환
만약 값이 null 일 경우 NullPointerException을 발생시킴 Optional<String> opt = Optional.of("value");
- ofNullable(T value) : 값ㅣ null 일수 있는 경우 사용한다
값이 null이면 비어있는 Optional객체를 null이 아니면 해당 값을 가지는 Optional객체를 반환한다 Optional<String> opt = Optional.ofNullable(변수);
- isPresent() : Optional객체 값 여부에 따라 true, false반환
if(opt.isPresent()){}
- get() : Optional객체가 가지고 있는 값을 반환한다, 비어있는 경우 NoSuchElementExceptiondmf 발생시킨다
String value = opt.get();
- ifPresent(Consumer<? super T> consumer) : 값이 존재하면 주어진 cunsumer를 값에 적용한다
opt.ifPresent(value -> System.out.println(value));
- orElse(T other) : Optional 객체가 비어있지 않으면 값을 반환하고 비어있다면 주어진 other 값을 대신 반환한다
String value = opt.orElse("default");
그 외에도 다양한 메서드가 있으니 필요할때마다 검색해봐야 할 것 같다
테스트코드 작성의 중요성
-
- test코드는 빌드될때 포함되지않는다
- 한글로도 작성이 가능하다
- given , when, then 문법을 활용하자
- 테스트코드는 정상 여무도 중요하지만 예외가 터지는지 예외 처리는
잘 되는지 검증도 해야한다.
정상 여부만 확인한다면 반쪽짜리이다 - 의존성 주입을 통해 final 객체도 동일한 인스턴스가 유지되도록
하는 것이 좋다
'🌱 𝐅𝐫𝐚𝐦𝐞𝐰𝐨𝐫𝐤 > ⠀⠀⠀⠀ SᴛʀɪɴɢBᴏᴏᴛ' 카테고리의 다른 글
Spring Boot - 웹 MVC개발 :: 회원 가입 구현 (0) | 2023.12.27 |
---|---|
Spring Boot - 스프링 빈과 의존관계 (1) | 2023.12.27 |
Spring Boot - Spring의 API (0) | 2023.12.27 |
Spring Boot - MVC와 템플릿 엔진 (0) | 2023.12.27 |
Spring Boot - 정적컨텐츠 (1) | 2023.12.27 |