회원관리를 위한 객체, 인터페이스, 리포지토리 생성 방법
강의 내용 복습 : 김영한의 스프링 입문 강의 (섹션 3)
🔔 강의 내용
📌 비즈니스 요구사항 정리
- 회원을 관리하는 서비스를 개발하려고 합니다.
- 데이터는 회원 ID (id) 및 이름(name) 두 가지를 다뤘습니다.
- 기능은 회원 등록 및 조회 두 가지를 다뤘습니다.
- 데이터 저장소 공간의 경우 아직 선정되지 않은 것으로 가정하였습니다.
📌 일반적인 웹 애플리케이션 계층 구조
- 본 회원 관리 웹 애플리케이션의 계층은 아래 네 가지로 구분됩니다.
- 컨트롤러 : 외부 요청을 받는 역할을 합니다.
- 서비스 : 비즈니스 로직을 구현하는 핵심 역할을 합니다.
- 리포지토리 : 데이터 및 도메인 객체를 데이터베이스에 저장하는 역할을 합니다.
- 도메인 : 각 비즈니스의 도메인 객체이며 명칭은 Member, Order 등으로 부여됩니다.
🔔 회원 관리를 위한 객체 생성
📌 Member 객체
- 회원 관리를 위해 요구사항의 데이터(id, name) 관련 회원 객체를 생성하였습니다.
- 각 데이터를 다른 객체에서 관리하기 위해 getter & setter 메서드를 생성하였습니다.
📌 MemberRepository 인터페이스
- 데이터 저장소와 상호작용시기 위해 인터페이스 역할의 MemberRepository를 생성하였습니다.
- 그런데 아직 데이터 저장소가 선정되지 않은 상태에기 때문에 임시 목적의 저장소가 필요합니다.
- 그래서 ‘임시 저장소’ 역할을 하는 MemoryMemberRepository를 생성하였습니다.
- 임시 저장소인 메모리 기반의 데이터 저장소는 새로운 저장소가 선정되면 삭제될 예정입니다.
- 인터페이스가 존재하기 때문에 변경되는 저장소에 맞춰 메모리의 코드를 이동시킬 예정입니다.
📌 MemoryMemberRepository 메모리 구현체
- 데이터를 임시적으로 저장하기 위한 메모리 구현체를 생성하였습니다.
- 그런데 해당 구현체는 동시성 문제가 고려되지 않았으며 HashMap을 사용하였습니다.
- 동시성 문제를 고려하려면 ConcurrentHashMap 또는 AtomicLong 등을 사용해야 됩니다.
- ID의 경우 sequence를 이용하여 시스템이 ID를 자동으로 부여하게 설정하였습니다.
🔔 개념 보충
📌 Optional & List
- ‘Optional’은 null이 아닌 객체를 담기 위해 사용되는 ‘컨테이너 객체’입니다.
- Optional은 null을 그대로 반환하는 것이 아니라 optional로 감싸서 반환하는 기능을 제공합니다.
- Optional 객체는 값이 있을 수도 있고 없을 수도 있음을 나타내기 위해 null 대신 사용됩니다.
- ‘List’는 순서가 지정된 컬렉션을 다루기 위한 메서드를 제공하는 ‘인터페이스’입니다.
- 상세한 설명은 아래 리포지토리 인터페이스 코드에 주석으로 작성하였습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
// save 메서드는 데이터베이스에 데이터를 저장하기 위해 사용됩니다.
// 여기에서는 Member 객체를 인자로 받고 Member 객체의 데이터를 DB에 저장합니다.
// 저장이 완료되면 저장된 Member 객체를 반환합니다.
// 활용 예시는 아래와 같습니다.
// 1. 사용자의 회원가입 시 회원 데이터를 담고 있는 Member 객체를 생성합니다.
// 2. sava 메서드를 호출하여 데이터를 데이터베이스에 저장합니다.
// 3. 회원의 기존 정보를 수정한 경우 수정된 Member 객체를 save 메서드에 전달합니다.
// 4. Member 객체를 save 메서드에 전달하면 변경사항이 저장됩니다.
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
// Member 객체의 Optional을 반환합니다.
// 이는 결과나 Member 객체를 포함할 수도 있고 안 할 수도 있다는 것을 의미합니다.
// 사례 1. 만약 id 또는 name을 가진 Member 객체가 존재하면 Optional은 해당 Member 객체를 포함합니다.
// 사례 2. 만약 id 또는 name을 가진 Member 객체가 없다면 Optional은 비어있게 됩니다.
// 따라서 결과가 null인 경우를 처리하는 코드를 작성할 때 발생될 수 있는 문제를 방지할 수 있습니다.
// 이는 최근 Optional 객체를 사용하는 이유입니다.
List<Member> findAll();
// Member 객체의 리스트를 반환합니다.
// 저장되어 있는 모든 Member 객체들을 순서대로 포함시키기 때문에 반복문 적용이 용이합니다.
// 따라서 여러 Member 객체들을 관리할 수 있고 특정 요소에 접근하는 것이 가능합니다.
}
📌 @Override
- Override는 하위 클래스에서 상위 클래스를, 또는 인터페이스를 재정의할 때 사용됩니다.
- 본 애플리케이션의 MemoryMemberRepository는 하위 클래스입니다.
- 본 애플리케이션의 MemberRepository는 데이터 저장소의 인터페이스입니다.
- 따라서 메모리 저장소에서 리포지토리 내용을 재구성하기 위해 Override가 필요하였습니다.
- Java에서 implements 키워드의 경우 후행되는 인터페이스의 메서드를 구현하기 위해 사용됩니다.
- Java에서 implements 및 메서드를 입력 후 메서드를 implement하면 @Override가 작성됩니다.
📌 HashMap, ConcurrentHashMap, AtomicLong
- HashMap은 Map 인터페이스의 일반적인 구현체이며 키-값 형태로 데이터를 저장합니다.
- ConcurrentHashMap은 HashMap보다 안전하며 멀티스레드 환경에서 동시성을 보장합니다.
- AtomicLong은 멀티스레드 환경에서도 long 값에 대한 원자적 연산을 보장합니다.
- 멀티스레드(multithreading)란 프로세스 내에서 작업을 병렬적으로 처리하는 것입니다.
- 원자적(atomic) 연산이란 연산이 완전히 실행되거나 아예 실행되지 않는 것을 의미합니다.
- 상세한 설명은 아래 메모리 저장소 코드에 주석으로 작성하였습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
// store는 모든 Member 객체를 저장하는 HashMap입니다.
// sequence는 Member 객체의 고유 ID를 생성하기 위해 사용되었습니다.
@Override
public Member save(Member member) {
member.setId(++sequence);
// Member 객체의 ID를 자동으로 증가시켜서 고유한 ID를 가지도록 설정하였습니다.
store.put(member.getId(), member);
// store에 Member 객체의 생성된 ID와 Member 객체를 저장하였습니다.
return member;
// 저장된 Member 객체를 반환하도록 설정하였습니다.
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
// 주어진 ID에 해당되는 Member 객체를 반환하도록 설정하였습니다.
// 객체가 없을 경우 Null 값 대신 Optional을 사용하도록 설정하였습니다.
}
@Override
public Optional<Member> findByName(String name) {
// 회원을 이름으로 검색하는 기능을 구현합니다.
// stream API를 이용하여 store 내의 Member 객체 중 name이 일치하는 첫 번째 객체를 찾습니다(= 필터링합니다).
return store.values().stream()
// store에 저장된 모든 Member 객체를 스트림 형태로 변환합니다.
.filter(member -> member.getName().equals(name))
// 주어진 name과 일치하는 Member 객체를 필터링합니다(= 찾습니다).
.findAny();
// 일치하는 첫 번째 객체를 Optional로 반환합니다.
// 일치하는 객체가 없으면 Optional.empty()를 반환합니다.
}
@Override
public List<Member> findAll() {
// 회원 전체 목록 조회 기능을 구현합니다.
return new ArrayList<>(store.values());
// store에 저장된 모든 Member 객체를 List로 반환합니다.
}
public void clearStore() {
// 테스트 코드에서 프로그램 실행 시 초기화 된 데이터베이스를 사용하기 위해 작성하였습니다.
store.clear();
// store에 저장된 모든 데이터를 삭제합니다.
}
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.