본문 바로가기

SPRING/Spring

[스프링| 스프링 입문 | 코드로 배우는 스프링] 회원 도메인과 리포지토리 만들기

package hello.hellospring.domain;

public class Member {

    private Long id; // 시스템에 저장하는 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;
    }
}
package hello.hellospring.repository; // 패키지 선언, 코드의 네임스페이스를 정의합니다.

import hello.hellospring.domain.Member; // Member 클래스를 임포트합니다. Member 도메인 모델에 접근하기 위함입니다.
import java.util.List; // 자바 유틸리티에서 List 인터페이스를 임포트합니다.
import java.util.Optional; // 자바 유틸리티에서 Optional 클래스를 임포트합니다. 값이 없을 수도 있는 객체를 감싸 처리하기 위해 사용됩니다.

public interface MemberRepository { // MemberRepository 인터페이스 정의 시작
    Member save(Member member); // 회원 정보를 저장하고 저장된 정보를 반환하는 메서드
    
    Optional<Member> findById(Long id); // ID로 회원을 찾고 결과를 Optional로 감싸서 반환하는 메서드
    
    Optional<Member> findByName(String name); // 이름으로 회원을 찾고 결과를 Optional로 감싸서 반환하는 메서드
    
    List<Member> findAll(); // 모든 회원의 목록을 반환하는 메서드
}

Optional<Member>은 Java 8에서 도입된 Optional<T> 클래스를 활용하는 것으로, Member 타입의 객체가 존재할 수도 있고 존재하지 않을 수도 있는 상황을 보다 명확하고 안전하게 처리하기 위해 사용됩니다. Optional은 값을 감싸는 래퍼(wrapper) 클래스로, null 참조를 직접적으로 다루는 것을 피하고 관련된 문제들을 예방하는데 도움을 줍니다.

Optional의 주요 기능 및 이점:

  1. 값의 존재 여부 명시적 표현: Optional은 값이 있거나 없음을 명확하게 표현합니다. 이를 통해 개발자는 반환된 객체를 사용하기 전에 그 존재 여부를 검사해야 함을 인지하게 됩니다.
  2. NullPointerException 방지: null 값을 직접 다루면서 발생할 수 있는 NullPointerException을 방지할 수 있습니다. Optional은 값이 없는 상태를 안전하게 처리할 수 있는 API를 제공합니다.
  3. 조건문 간소화: Optional은 값이 있는지 여부에 따라 실행할 로직을 체이닝(chaining) 방식으로 간결하게 표현할 수 있습니다. 이는 코드를 더 읽기 쉽고 간결하게 만들어 줍니다.
  4. 함수형 프로그래밍 지원: Optional은 map, flatMap, filter 등의 메서드를 지원하여 함수형 프로그래밍 스타일의 연산을 가능하게 합니다. 이는 코드를 선언적으로 작성할 수 있게 도와주며, 변환 과정에서 null을 신경 쓸 필요가 없습니다.
public Optional<Member> findById(Long id) {
    return Optional.ofNullable(store.get(id));
}
// Optional 사용 시 값의 존재를 확인하고 처리하는 방법
Member result = repository.findById(memberId)
                          .orElseThrow(() -> new NoSuchElementException("No such element"));

Optional의 주의점:

  • 반환 타입으로만 사용: Optional은 메서드의 반환 타입으로만 사용하는 것이 권장됩니다. 필드 타입, 메서드 파라미터 타입, 생성자 파라미터 타입으로 사용하는 것은 권장되지 않습니다.
  • 컬렉션과 함께 사용 금지: 반환 값이 컬렉션이라면 빈 컬렉션을 반환하는 것이 Optional을 사용하는 것보다 낫습니다. Optional의 주된 목적은 단일 값의 존재 여부를 표현하는 것입니다.

Optional을 사용함으로써 코드의 안정성을 높이고, 개발자의 의도를 명확하게 전달할 수 있습니다. 이는 프로젝트의 유지보수성을 크게 향상시킬 수 있습니다.

 

package hello.hellospring.repository; // 패키지 선언

import hello.hellospring.domain.Member; // Member 클래스를 임포트합니다.
import java.util.*; // 자바 유틸 패키지에서 모든 클래스를 임포트합니다.

public class MemoryMemberRepository implements MemberRepository{ // MemberRepository 인터페이스를 구현하는 MemoryMemberRepository 클래스

    private static Map<Long, Member> store = new HashMap<>(); // 회원 정보를 저장할 HashMap 객체 생성. 모든 인스턴스가 공유하는 static 변수
    private static long sequenc = 0L; // 회원 ID를 생성하기 위한 sequence, static으로 모든 인스턴스가 공유

    @Override
    public Member save(Member member) { // 회원 저장 메서드 구현
        member.setId(++sequenc); // 회원 ID 자동 증가
        store.put(member.getId(), member); // HashMap에 회원 ID와 회원 정보 저장
        return member; // 저장된 회원 정보 반환
    }
    
    @Override
    public Optional<Member> findById(Long id) { // ID로 회원 찾는 메서드 구현
        return Optional.ofNullable(store.get(id)); // HashMap에서 ID로 회원 정보 검색, 결과가 null일 수 있으므로 Optional로 감싸 반환
    }

    @Override
    public Optional<Member> findByName(String name) { // 이름으로 회원 찾는 메서드 구현
        return store.values().stream() // HashMap의 값 목록을 스트림으로 생성
                .filter(member -> member.getName().equals(name)) // 스트림에서 회원 이름이 입력 이름과 일치하는지 필터링
                .findAny(); // 일치하는 첫 번째 회원을 반환, 없으면 Optional.empty 반환
    }
    
    @Override
    public List<Member> findAll() { // 모든 회원 목록 반환 메서드 구현
        return new ArrayList<>(store.values()); // HashMap의 값 목록을 ArrayList로 변환하여 반환
    }
}

Map과 HashMap

Map

  • Map은 Java의 컬렉션 프레임워크에서 키-값 쌍으로 데이터를 저장하는 자료구조 인터페이스입니다. 각 키는 맵 내에서 유일하며, 각 키에는 하나의 값만이 매핑됩니다. Map 인터페이스의 다양한 구현체로는 HashMap, TreeMap, LinkedHashMap 등이 있습니다.

HashMap

  • HashMap은 Map 인터페이스를 구현한 가장 일반적인 맵입니다. 내부적으로 해시 테이블을 사용하여 키-값 쌍을 저장합니다. HashMap은 요소의 순서를 보장하지 않으며, 키와 값 모두 null이 될 수 있습니다. HashMap은 주로 빠른 조회, 삽입, 삭제 연산이 필요할 때 사용됩니다.
  • 성능: HashMap의 기본 연산(삽입, 삭제, 검색)은 평균적으로 O(1)의 시간 복잡도를 가집니다. 하지만, 해시 충돌이 많이 발생하는 경우 최악의 경우 O(n)까지 갈 수 있습니다.

sequenc의 사용 의미

sequenc는 데이터베이스에서 일반적으로 사용되는 자동 증가(auto-increment) 필드와 유사한 기능을 수행합니다. 메모리 내에서 데이터를 관리할 때 고유한 식별자를 생성하기 위해 사용됩니다. 이 변수를 통해 각 Member 객체에 대해 고유하고 연속적인 ID 값을 제공하여, 키로서의 역할을 할 수 있게 합니다. 이렇게 함으로써 Member 객체의 검색, 수정, 삭제가 용이해집니다.

 

 

Stream

  • Java 8에서 도입된 Stream API는 컬렉션에 저장된 요소를 함수형 스타일로 처리할 수 있게 해줍니다. 이를 통해 데이터를 선언적으로 다루면서 복잡한 집계, 필터링, 변환 작업을 간단하게 처리할 수 있습니다. 스트림은 데이터를 변경하지 않고, 새로운 스트림을 반환하는 연산을 통해 데이터를 처리합니다.

Filter

  • filter는 스트림의 각 요소에 대해 테스트를 수행하고, 테스트를 통과한 요소만을 포함하는 새 스트림을 생성합니다. 이 연산은 스트림의 중간 연산으로, 다른 연산과 연결될 수 있습니다.

Lambda Expressions

  • 람다식은 간결한 방식으로 익명 함수를 표현할 수 있게 해줍니다. 자바에서 람다식은 파라미터, 화살표(->), 그리고 바디로 구성됩니다. 람다식은 함수형 인터페이스의 인스턴스를 생성하는 데 사용됩니다.

findAny

  • findAny는 스트림의 요소 중 하나를 선택하여 반환합니다. 이 메서드는 병렬 처리 환경에서 유용하며, 첫 번째 요소가 아닌 어떤 요소든 반환할 수 있습니다. 반환된 요소는 Optional로 감싸져 있어서, 값이 없는 경우를 안전하게 처리할 수 있습니다.