Spring boot - 회원 관리 예제

범수·2024년 2월 19일

목차

  1. 프로젝트 생성하기
  2. View 환경설정
  3. 스프링 웹 개발 기초
  4. 회원 관리 예제
  5. 스프링 빈과 의존 관계
  6. 웹 MVC 제작
  7. DB 제작(1)
  8. DB 제작(2)
  9. AOP

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

요구사항

  • 데이터: 회원ID, 이름
  • 기능: 회원 등록, 조회
  • 아직 데이터 저장소가 선정되지 않음(가상의 시나리오)

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

회원 객체

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 and setter를 이용해 id, name에 관한 Member클래스 생성
    • 단축키 Alt + Insert 사용

회원 리포지토리

 public interface MemberRepository {
    Member save(Member member);
    //null값을 optional로 가공 후 반환
    Optional<Member> findById(Long id); 
    Optional<Member> findByName(String name);
    List<Member> findAll();
}
// 회원 리포지토리 인터페이스
  • 리포지토리에 가공하기 위한 기본적인 인터페이스들을 생성
public class MemoryMemberRepository implements MemberRepository{

    private  static Map<Long, Member> store = new HashMap<>();
    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));  //NULL값 반환 시 Optional로 감싸서 NULL값을 반환
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()     // 루프
                .filter(member -> member.getName().equals(name))  // name과 값이 같을 시 필터링
                .findAny();     // 찾으면 반환
    }

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

    public void clearStore(){
        store.clear();
    }
}
//회원 리포지토리 메모리 구현체
  • 생성한 인터페이스의 기능을 구현
    • Map<>을 통해 회원 정보 저장
    • 편리성을 위해 List findAll()에서 List 사용
    • store.clear(); 함수는 리포지토리 초기화해주는 함수

테스트 케이스 작성

  • 기존에는 main메소드, 컨트롤러에서 테스트를 진행
    • 준비과정, 반복진행 등 어려움 발생
  • JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결
class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

  
  	@AfterEach	// 각 테스트 진행 후 동작
    public void afterEach(){
        repository.clearStore();
  	}	

    @Test
    public void save(){
  		//given
        Member member = new Member();
        member.setName("spring");
		//when
        repository.save(member);
		//then
        Member result = repository.findById(member.getId()).get();
        Assertions.assertEquals(result,member);   // (값, 비교값) 동일 시 무응답, 다를 시 값 반환
        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("spring1");
        repository.save(member2);

        List<Member> result = repository.findAll();
        assertThat(result.size()).isEqualTo(2);
    }
}
//회원 리포지토리 테스트 케이스
  • 단축키 Ctrl + Shift + T 사용
  • 각 테스트 케이스를 작성하여 main, 컨트롤러에서 하는 것 보다 안전하고 쉽게 테스트 가능
  • 전체 테스트 케이스 동작 시 findByName과 findAll의 member값이 중복되어 에러 발생
    • @AfterEach를 통해 각 테스트마다 store.clear(); 진행(스토리지 초기화)
  • assertThat(비교값).isEqualTo(비교 대상); 값이 같을 시 true, false를 반환(비교 함수)

3. 회원 서비스 개발

서비스 구현

public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {  
        this.memberRepository = memberRepository;
    }

    public Long join(Member member){    // 회원 가입
        validateDuplicateMember(member);    // 중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {   // 중복회원검증
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {                       // findByName값이 도출될 시 동작
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    public List<Member> findMembers(){
        return memberRepository.findAll();
    }   // 전체 맴버 출력
    //Java에서는 대부분 List 사용(편의성)
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }   // 회원 검색(Id)
}
// 회원 서비스 
  • 서비스는 비즈니스적인 요소(회원가입, 조회 등)를 구현
before> private final MemberRepository memberRepository = 
  new MemoryMemberRepository();
  
after> public MemberService(MemberRepository memberRepository) {
 	this.memberRepository = memberRepository;
  }
  • 회원 서비스가 메모리 회원 리포지토리를 직접 생성(사실 상 기존 리포지토리와 다른 메모리 리포지토리 생성)
    • 회원 리포지토리의 코드가 회원 서비스 코드를 DI 가능하게 변경

서비스 테스트 케이스

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("park");
        //when
        Long saveId = memberService.join(member);
        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());

    }

    @Test
    void 중복회원() {
        //given
        Member member1 = new Member();
        member1.setName("park");

        Member member2 = new Member();
        member2.setName("park");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> memberService.join(member2));//예외가 발생해야 한다.
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

        //then
    }
}
  • @BeforeEach 각 테스트 실행 전에 호출되어 테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고, 의존관계도 새로 맺어준다
  • 테스트 케이스는 main 코드가 아니므로 가독성을 위한 클래스 명으로 생성 가능(회원가입, 중복회원 등)
profile
범수의 개발 놀이터😋

0개의 댓글