반응형
1. 비즈니스 요구사항 정리
- 데이터 : 회원 ID, 이름
- 기능 : 회원 등록, 조회
- 데이터 저장소가 선정되지 않음 (가상의 시나리오)
- Controller : Web MVC의 Controller 역할 (API 생성 등)
- Service : 핵심 비즈니스 로직 구현 (중복 가입 불가 등 핵심 기능) <- 비즈니스 도메인 객체를 가지고 로직 구현
- Repository : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- Domain : 비즈니스 도메인 객체. 예시) 회원, 주문, 쿠폰 등등 주로 DB에 저장하고 관리
- 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계합니다.
(저장소를 변경하려면 인터페이스가 필요합니다.)
- 데이터 저장소는 RDB, NoSQL 등 다양한 저장소를 고민 중인 상황으로 가정합니다.
- 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소를 사용합니다.
Domain, Repository
2.1 domain 패키지 생성
2.2 Member.class 생성
package hellospring.hello.domain; public class Member { private Long id; // 시스템이 저장하는 임의의 값 입니다. 고객이 정하는게 아님! private String name; // 단순하게 만들기 때문에 Getter Setter를 사용합니다. 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.3 repository 패키지 생성
2.4 MemberRepository.interface 생성package hellospring.hello.repository; import hellospring.hello.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(); // 전체 회원 리스트 보기 // Optional : java8에서 추가 됨. // null을 그대로 반환하지 않고 Optional로 감싸서 반환하는 방법 }
2.5 MemoryMemberRepository 생성
- Option + Enter 메서드 구현을 클릭하시고 메서드를 만드시면 됩니다.
implements : interface를 상속할 땐 implements를 사용합니다. 여러 개의 interface를 상속받을 수 있으며, 자식 클래스는 부모의 기능을 다시 정의(오버라이딩)해서 사용해야 한다.
-> 사실, "이러한 구현은 부모의 메서드를 어차피 재정의해야 하니 '상속'의 의미가 없지 않나?"라고 할 수 있지만, Java와 c#의 인터페이스 상속은 계약 및 분류의 의미가 강하다고 합니다.
간단하게 눌러주었는데 메서드가 구현되었습니다.
이제 메서드에 기능을 추가할 것입니다.
save 기능을 사용하려면 어딘가에 저장을 해야 합니다.
// MemoryMemberRepository.java // 어딘가 저장을 해야되기 때문에 만든다. ... private static Map<Long, Member> store = new HashMap<>(); // id가 long 타입이라서 long private static Long sequence = 0L; // sequence는 0,1,2 등 키 값을 생성해주는 역할입니다 id 값이라고 생각하면 편합니다. ...
여기서 Map이란?
java에서는 자료구조를 Map이라고 이름 지었습니다.
Map에 저장되는 데이터는 ‘key-value’ pair라는 형식을 갖고 있으며 key와 value는 매칭 됩니다.
이는 ‘주민등록번호 - 사람 이름’ 관계와 비슷하다고 보면 됩니다.
주민등록번호는 한 명도 똑같은 사람이 없다. -> key값은 중복되지 않는다.
주민등록번호는 있는데 사람 이름이 없는 경우는 없다. -> key없는 value는 없음
이름이 있는 사람이 주민등록번호가 없는 경우는 없다. -> value없는 key는 없음
주민등록번호가 달라도 사람이름은 똑같을 수 있다.(동명이인) -> value는 중복 가능
참조 : https://onsil-thegreenhouse.github.io/programming/java/2018/02/22/java_tutorial_1-24/
실무에서는 동시성 문제가 있을 수 있어서 공유되는 변수일 때는 ConcurrentHashMap 을 사용해야 하나 예제라서 단순하게 사용
마찬가지로 sequence의 타입도 long보다는 동시성 문제를 고려해서 AtomicLong 을 사용해줘야 합니다.
기능 구현
이제 기능 구현을 하겠습니다.
<save>// MemoryMemberRepository.java // 회원 저장 기능 ... @Override public Member save(Member member) { member.setId(++sequence); // id값이 할당되고 점점 증가한다. store.put(member.getId(), member); return member; // member에 id와 name이 저장된다. } ...
<findByID>// MemoryMemberRepository.java // 회원 아이디 찾기 기능 ... @Override public Optional<Member> findById(Long id) { return Optional.ofNullable(store.get(id)); // null 값이 올수도 있으므로 ofNullable로 해준다. } ...
<findByName>// MemoryMemberRepository.java // 회원 이름으로 찾기 기능 ... @Override public Optional<Member> findByName(String name) { return store.values().stream() .filter(member -> member.getName().equals(name)) .findAny(); } ... // 람다식으로 사용되었으며 필터기능을 줘서 member.getName 값이 파라미터로 넘어온 name값이랑 같은 지 확인 // name이 같은 경우에는 필터링이 된다. // findAny()는 결과가 Optional로 반환된다. Map에서 루프를 다 돌면서 하나 찾으면 찾은 값을 반환 // 끝까지 돌렸는데 없으면 Optional에 null이 포함되서 반영된다.
<findAll>// MemoryMemberRepository.java // 회원 전체 검색하기 ... @Override public List<Member> findAll() { // 자바에서 실무에는 List를 많이 사용한다. < 루프 돌리기가 편함. return new ArrayList<>(store.values()); // store.values = member } ...
기능 구현은 이걸로 완성되었다. 다음에는 테스트코드로 기능이 제대로 동작하는지 확인!// MemoryMemberRepository.java 전체 코드 package hellospring.hello.repository; import hellospring.hello.domain.Member; import java.util.*; public class MemoryMemberRepository implements MemberRepository { private static Map<Long, Member> store = new HashMap<>(); // 실무에서는 동시성 문제가 있을 수 있어서 공유되는 변수일 때는 ConcurrentHashMap 을 사용해야 하나 예제라서 단순하게 사용 private static Long sequence = 0L; // sequence는 0,1,2 등 키 값을 생성해준다. 동시성 문제를 고려해서 AtomicLong 을 사용해줘야 합니다. @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 값이 올수도 있으므로 ofNullable로 해준다. } @Override public Optional<Member> findByName(String name) { return store.values().stream() .filter(member -> member.getName().equals(name)) .findAny(); } @Override public List<Member> findAll() { // 자바에서 실무에는 List를 많이 사용한다. < 루프 돌리기가 편함. return new ArrayList<>(store.values()); // store.values = member } }
반응형
'programming > SpringBoot' 카테고리의 다른 글
[spring] 회원 서비스 (회원 가입하기, 조회하기) (0) | 2022.07.05 |
---|---|
[spring] 테스트 코드 (0) | 2022.06.27 |
[Spring] 스프링 입문 강의 2 (콘텐츠의 종류 - 정적 콘텐츠, MVC, API) (0) | 2022.06.24 |
[Spring] 스프링 입문 강의 1 (김영한님 무료 강좌) (0) | 2022.06.16 |
새로운 프로젝트 시작! (0) | 2022.04.28 |