Cloud Native Spring in Action

스프링 MVC를 이용한 RESTful 애플리케이션 구축(1)

기록해연 2025. 2. 5. 15:27

이제 필요한 소스를 만드는 중인데, 중간중간 잘 모르는 메소드나 타입, 어노테이션 등을 chatGPT에게 물어봐서 답변 받았던 거 중 몇가지 아카이브(?). 책, 챗지한테서 나온 답변 걍 복사해넣고 아주 조금 다듬은 정도.

 


 

개체 Entity : 개체는 한 도메인에서 명사를 나타낸다. / ex. 책

서비스 Service: 서비서는 도메인에 대한 사용사례를 정의하고 구현한다. / ex. 카탈로그에 책 추가

리포지터리 Repository: 데이터 소스와 독립적으로 데이터에 엑세스하기 위한 추상화 계층이다.


 

📌 BookRepository 인터페이스의 인메모리 구현이란?

✅ "인메모리 구현"이란, 데이터를 실제 데이터베이스(DB)에 저장하지 않고, 애플리케이션의 메모리(RAM)에서 저장하고 관리하는 방식을 의미합니다.
✅ 즉, BookRepository가 데이터베이스 대신 ConcurrentHashMap 같은 컬렉션을 이용해 데이터를 저장하는 방식으로 구현된 것을 뜻합니다.

 

 

🚀 인메모리 구현 vs 데이터베이스 구현

구현 방식 설명 예제

인메모리 구현 데이터를 애플리케이션 실행 중 메모리(RAM)에 저장 Map<String, Book> books = new ConcurrentHashMap<>();
데이터베이스(DB) 구현 데이터를 실제 데이터베이스(MySQL, PostgreSQL 등)에 저장 JPA, Hibernate를 사용하여 findById() 실행

📌 인메모리 방식의 특징

빠름 → DB를 거치지 않고 RAM에서 직접 데이터 조회
애플리케이션이 종료되면 데이터 사라짐
테스트에 많이 사용됨 (실제 DB 없이도 테스트 가능)

 

🔥 인메모리 구현 예제 (ConcurrentHashMap 사용) 104p

 

package com.polarbookshop.catalogservice.persistence;

import com.polarbookshop.catalogservice.domain.Book;
import com.polarbookshop.catalogservice.domain.BookRepository;
import org.springframework.stereotype.Repository;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

// BookRepository 인터페이스의 인메모리 구현
// @Repository: 클래스가 스프링에 의해 관리되는 리포지터리임을 표시
@Repository
public class InMemoryBookRepository implements BookRepository {
    private static final Map<String, Book> books = new ConcurrentHashMap<>();

    @Override
    public Iterable<Book> findAll() {
        return books.values();
    }

    @Override
    public Optional<Book> findByIsbn(String isbn) {
        return existsByIsbn(isbn) ?  Optional.of(books.get(isbn)) : Optional.empty();
    }

    @Override
    public boolean existsByIsbn(String isbn) {
        return books.get(isbn) != null;
    }

    @Override
    public Book save(Book book) {
        books.put(book.isbn(), book);
        return book;
    }

    @Override
    public void deleteByIsbn(String isbn) {
        books.remove(isbn);
    }

}

ConcurrentHashMap<>

private static final Map<String, Book> books = new ConcurrentHashMap<>();

 

📌 ConcurrentHashMap<>란?

  • HashMap과 비슷하지만 멀티스레드 환경에서도 안전(Thread-safe)한 맵입니다.
  • 동시에 여러 스레드가 데이터를 읽고 써도 오류 없이 동작하도록 설계되어 있습니다.
  • 일반적으로 HashMap은 멀티스레드에서 안전하지 않기 때문에, 여러 스레드가 접근할 가능성이 있는 경우 ConcurrentHashMap을 사용합니다.

📌 ConcurrentHashMap<> vs HashMap<> 비교

맵 타입멀티스레드 안전 여부동시 수정 가능 여부성능

HashMap<> ❌ 안전하지 않음 ❌ 불가능 빠름
ConcurrentHashMap<> ✅ 안전함 ✅ 가능 조금 느림

 

📌 예제

Map<String, Book> books = new ConcurrentHashMap<>();
books.put("12345", new Book("12345", "Java Basics"));
System.out.println(books.get("12345"));  // Java Basics

➡ 여러 스레드가 books.put()을 동시에 실행해도 문제가 발생하지 않음.

 


Optional<T>

public Optional<Book> findByIsbn(String isbn)

 

📌 Optional<T>란?

  • null을 직접 반환하는 대신, 값이 있을 수도 있고 없을 수도 있는 상황을 표현하기 위한 클래스입니다.
  • null을 반환하면 NullPointerException이 발생할 수 있지만, Optional<T>를 사용하면 이를 방지할 수 있습니다.
  • Optional<T> 내부적으로 값이 있으면 반환하고, 값이 없으면 empty 상태로 유지합니다.

📌 Optional<Book>의 의미

public Optional<Book> findByIsbn(String isbn)

 

➡ findByIsbn() 메서드는 책을 찾으면 Book을 반환하고, 찾지 못하면 Optional.empty()를 반환합니다.

 


 

Optional.of() & Optional.empty()

return existsByIsbn(isbn) ? Optional.of(books.get(isbn)) : Optional.empty();
 

📌 Optional.of(value)

  • 값이 존재할 때 Optional<T> 객체를 생성합니다.
  • Optional.of(books.get(isbn))는 isbn에 해당하는 Book 객체가 존재하면, Optional<Book>을 만들어 반환합니다.

📌 Optional.empty()

  • 값이 없을 때 Optional<T> 객체를 생성합니다.
  • Optional.empty()는 null 대신 안전하게 "값이 없음"을 표현하는 객체를 반환합니다.

📌 Optional.of() vs Optional.ofNullable() 차이

메서드null 허용 여부설명

Optional.of(value) ❌ null이면 NullPointerException 발생 값이 무조건 있을 때 사용
Optional.ofNullable(value) ✅ null일 수도 있음 값이 없을 수도 있을 때 사용

 

📌 예제

Book book = books.get("12345"); // 책이 존재할 수도 있고, 없을 수도 있음 
Optional<Book> optionalBook = Optional.ofNullable(book); // 안전하게 Optional로 감싸기
 

➡ book이 null일 경우에도 Optional.empty()로 안전하게 처리됨.


🎯 최종 정리

코드 요소설명

ConcurrentHashMap<> 멀티스레드 환경에서도 안전한 HashMap
Optional<Book> 값이 없을 수도 있는 상황을 안전하게 처리
Optional.of(value) 값이 있을 때 Optional<T>로 감싸 반환
Optional.empty() 값이 없을 때 안전하게 반환