이번 글에서는 회원(도메인) 객체 생성, 회원을 저장할 저장소 인터페이스, 마지막으로 저장소 인터페이스를 메모리 형태로 구현하는 과정을 복기 해봅니다.
도메인: 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됩니다.
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; //인스턴스 변수에 setName 의 인자로 넘어온 값 할당
}
}
getter 와 setter 가 있는 간단한 객체 입니다.
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();
}
인터페이스는 다른 클래스 작성시 필요한 틀이라고 이해하면 되겠습니다.
Optional 은 Null 이 올 수 있는 곳을 감싸는 Wrapper 클래스 입니다.
NullPointerException을 방지할 수 있죠.
DB가 정해지지 않았기에 메모리로 구현합니다.
동시성 문제가 고려되어 있지 않습니다. 실무에서는 ConcurrentHashMap, AtomicLong 사용을 고려 해볼 수 있습니다.
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;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
}
@Override 를 통해 메서드 내용을 작성합니다.
save 메서드는 생성된 회원 인스턴스를 인자로 받습니다.
회원의 인스턴스의 Id 값은 save 매서드에서 정하고 있네요.
@Override
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
}
id 값에 맞는 회원을 찾습니다. 저장소에 있지 않은 id 값을 인자로 받으면 NullpointerException이 발생할텐데요,Optianal.ofNullabe 로 감싸서 return 하면 클라이언트 측에서 대응 할 수 있습니다.
@Override
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
findAll 메서드는 회원 객체를 요소로 가지는 리스트를 반환 합니다.
ArrayList 의 생성자의 인자가 store.values() 인데,
store.values 는 map의 value 값들을 Collection 형태로 반환 합니다.
@Override
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
}
findByName 에서는 람다식을 사용하고 있습니다. 아직은 친한 문법이 아니라서 적응하려면 시간이 필요할거 같네요.
위에서 언급 했지만 stroe.values() 를 통해 컬렉션 타입을 얻고, 컬렉션 타입에서 .stream() 메서드를 통하여 스트림을 생성 할 수 있습니다.
member -> : 메서드의 인자라고 보면 됩니다.
람다식 인자로 받은 회원 객체의 이름과 findByName 의 인자 값과 비교하여 같은 것만 스트림에 남깁니다. 이후 findAny() 를 통해 하나의 요소만 Optional 형태로 반환 합니다.
추가로 findAny() 와 findFirst() 의 차이는 Stream 을 병렬로 다룰 때 findFirst() 는 항상 같은 값을 반환 하지만 findAny()는 요소가 여러개면 다른것을 반환 할때도 있습니다.