만들기 전에 비즈니스 요구 사항을 정리해야 한다.
1. 비즈니스 요구사항 정리
- 데이터 : 회원ID, 이름
- 기능 : 회원 등록, 조회
- 아직 데이터 저장소가 선정되지 않음
- 컨트롤러 : mvc의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현
- 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB 에 저장하고 관리
- 도메인 : 비즈니스 도메인 객체, 데이터베이스에 주로 저장되는 비즈니스 도메인 객체
예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리된다.
회원 도메인과 리포지토리 만들어보기
1. 인터페이스 만들기
package domain.repository;
import domain.Member;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
//id 회원을 찾는 함수
//Optional 은 자바 파일 안에 들어 간것 자바안에 들어가 있는 기능
Optional<Member> findById(long id);
Optional<Member> findByName(String name);
//지금까지 저장된 회원 정보를 다 반환한다.
List<Member> findAll();
}
- save(Member member)
→ 회원 저장 - findById(long id)
→ ID로 회원 찾기 - findByName(String name)
→ 이름으로 회원 찾기 - findAll()
→ 모든 회원 리스트 가져오기
2. 구현 클래스
MemoryMemberRepository (구현 클래스)
2-1. 회원을 저장할 Map
private Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
Map이란?
- Map은 리스트나 배열처럼 순차적으로(sequential) 해당 요소 값을 구하지 않고 key를 통해 value를 얻는다.
- 맵(Map)의 가장 큰 특징이라면 key로 value를 얻어낸다는 점이다.
- Key는 중복될 수 없지만 , Value는 중복될 수 있다
HashMap 이란?
- **put(K key, V value)**
- **데이터를 삽입**하는 메써드이다.
- List와 Set과는 다르게 put을 활용한다
- 회원 정보를 저장할 공간을 Map으로 만듦 (store)
- Map<Long, Member>
→ Key: id, Value: Member 객체
- Map<Long, Member>
- sequence
- 회원 ID를 자동 증가시키기 위해 사용.
2-2. 회원 저장 (save())
@Override
public Member save(Member member) {
member.setId(++sequence); // 회원 ID 자동 증가
store.put(member.getId(), member); // store(Map)에 저장
return member; // 저장한 회원 반환
}
예제)
Member member1 = new Member();
member1.setName("홍길동");
Member savedMember = repository.save(member1);
store에 { 1L : "홍길동" } 저장됨.
2-3. findById() (ID로 회원 찾기)
@Override
public Optional<Member> findById(long id) {
return Optional.ofNullable(store.get(id));
}
- store.get(id) → store(Map)에서 해당 id에 해당하는 회원을 찾음.
- Optional.ofNullable()
→ 값이 없으면 null 대신 Optional.empty() 반환 (오류 방지).
2-4. findByName() (이름으로 회원 찾기)
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(m -> m.getName().equals(name))
.findAny();
}
- store.values() → Map에서 모든 회원 정보를 가져옴.
- .stream().filter(m -> m.getName().equals(name))
→ 이름이 같은 회원을 찾음. - .findAny()
→ 찾은 회원 중 아무거나 하나 반환.
예제)
repository.save(new Member("홍길동"));
repository.save(new Member("김철수"));
Optional<Member> result = repository.findByName("김철수");
결과: "김철수" 회원 객체 반환.
2-5. findAll() (모든 회원 리스트 반환)
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
- store.values() → Map에 저장된 모든 회원 정보를 가져옴.
- new ArrayList<>(store.values())
→ 리스트(List)로 변환해서 반환.
그럼 내가 이렇게 만들고 정상적으로 동작하는지 아는 방법
package org.example.springstudy.repository;
import domain.Member;
import domain.repository.MemberRepository;
import domain.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
Assertions.assertThat(member).isEqualTo(result);
}
}
Assertions 을 통해 값이 제대로 출력되는지 확인할 수 있다.
@Test
public void findById() {
Member member1 = new Member();
member1.setName("회원1");
repository.save(member1);
Member member2 = new Member();
member2.setName("회원2");
repository.save(member2);
Member result = repository.findById(member1.getId()).get();
Assertions.assertThat(result).isEqualTo(member1);
이러면 member1 객체에 있는 것을 가져와서 정상적으로 뜬다.
- findById(long id)는 회원 ID(long)로 회원을 찾는 메서드임.
- 하지만 member.getName()은 String 타입의 이름이라서, findById()에 넣으면 타입 오류 발생.
Service 생성
package service;
import domain.Member;
import repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
public class Memberservice {
private final MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();
// 회원 가입
public Long join(Member member) {
validateDuplicateMember(member); // 중복 회원 체크
Member savedMember = memoryMemberRepository.save(member); // 저장 후 ID 가져오기
return savedMember.getId();
}
// 중복 회원 검증 메서드
private void validateDuplicateMember(Member member) {
memoryMemberRepository.findByName(member.getName())
.ifPresent(member1 -> {
throw new IllegalArgumentException("Member already exists");
});
}
// 전체 회원 조회
public List<Member> findMembers() {
return memoryMemberRepository.findAll(); // ✅ 회원 목록 반환
}
// ID로 회원 조회
public Optional<Member> findMemberById(Long id) {
return memoryMemberRepository.findById(id);
}
}
List<Member>를 사용하는 이유
- 여러 개의 회원이 존재할 수 있기 때문
- findAll()은 저장된 모든 회원을 가져오는 메서드이므로, 반환값이 배열처럼 여러 개의 회원 정보를 담을 수 있어야 함.
- List는 여러 개의 데이터를 저장하는 컬렉션 타입이므로 적절함.
Optional<Member>를 사용하는 이유
- ID로 조회하는 회원은 "있을 수도 있고, 없을 수도 있음".
- 만약 null을 반환하면 NullPointerException(NPE) 오류가 발생할 수 있음.
- Optional<Member>를 사용하면 회원이 없을 경우 Optional.empty()를 반환하여 NPE를 방지할 수 있음.
회원서비스 테스트 해보기
package service;
import domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MemberserviceTest {
Memberservice memberservice = new Memberservice();
@Test
void join() {
//given
Member m = new Member();
m.setName("회원1");
//when
Long saveId = memberservice.join(m);
//then
Member findMembers = memberservice.findMemberById(saveId).get();
Assertions.assertThat(m.getName()).isEqualTo(findMembers.getName());
}
'Spring' 카테고리의 다른 글
[Spring] Spring으로 변환하기 (0) | 2025.03.23 |
---|---|
[Spring] 주문과 할인 도메인 설계 (0) | 2025.03.16 |
[Spring] 회원등록 불러오기 (0) | 2025.03.04 |
[Spring] 웹 기초 (0) | 2025.03.01 |