본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 21. · 3 Views
Spring MongoDB 완벽 가이드
MongoDB와 NoSQL의 핵심 개념부터 Spring Data MongoDB를 활용한 실전 CRUD까지, 초급 개발자도 쉽게 따라할 수 있는 완벽한 가이드입니다. JPA와의 비교를 통해 언제 MongoDB를 사용해야 하는지 명확하게 이해할 수 있습니다.
목차
1. MongoDB 소개
어느 날 김개발 씨가 쇼핑몰 프로젝트를 진행하던 중, 상품 정보를 저장하는 방식에 대해 고민하게 되었습니다. 기존에 사용하던 MySQL로는 자주 변경되는 상품 속성들을 다루기가 너무 복잡했습니다.
"더 유연한 방법은 없을까요?" 선배 박시니어 씨가 다가와 말했습니다. "MongoDB를 한번 알아보는 게 어때요?"
MongoDB는 문서 지향 NoSQL 데이터베이스입니다. 마치 JSON 형태로 데이터를 저장하는 거대한 파일 캐비닛과 같습니다.
테이블과 행이 아닌 컬렉션과 문서로 데이터를 관리하며, 스키마가 유연해서 빠르게 변화하는 요구사항에 적응하기 쉽습니다. 대용량 데이터 처리와 확장성이 뛰어나 많은 현대적인 애플리케이션에서 활용되고 있습니다.
다음 코드를 살펴봅시다.
// MongoDB 문서 구조 예시 (JSON 형식)
{
"_id": "507f1f77bcf86cd799439011",
"name": "갤럭시 S24",
"price": 1200000,
"category": "스마트폰",
"specs": {
"color": "블랙",
"storage": "256GB",
"ram": "12GB"
},
"tags": ["5G", "안드로이드", "삼성"],
"reviews": [
{"user": "user1", "rating": 5, "comment": "훌륭합니다"}
]
}
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 쇼핑몰 프로젝트를 진행하던 중 골치 아픈 문제를 만났습니다.
상품마다 저장해야 할 정보가 너무 다양했기 때문입니다. 스마트폰은 색상, 용량, RAM 정보가 필요한데, 의류는 사이즈, 소재, 색상이 필요합니다.
가전제품은 또 다른 속성들이 필요했습니다. MySQL로 이를 처리하려니 테이블이 점점 복잡해지고, 컬럼을 자주 추가하고 수정해야 했습니다.
선배 개발자 박시니어 씨가 다가와 모니터를 살펴봅니다. "음, 이런 경우에는 MongoDB가 딱이네요." 그렇다면 MongoDB란 정확히 무엇일까요?
쉽게 비유하자면, MongoDB는 마치 자유로운 형식의 노트와 같습니다. 일반적인 관계형 데이터베이스가 정해진 양식의 서류철이라면, MongoDB는 각 페이지마다 다른 형식으로 정보를 기록할 수 있는 자유로운 노트입니다.
어떤 페이지에는 표를 그리고, 어떤 페이지에는 그림을 그려도 됩니다. 이처럼 MongoDB도 각 데이터마다 다른 구조를 가질 수 있습니다.
관계형 데이터베이스만 사용하던 시절에는 어땠을까요? 개발자들은 모든 데이터를 정형화된 테이블 구조에 맞춰야 했습니다.
새로운 속성이 필요할 때마다 테이블 구조를 변경해야 했고, 이는 서비스 중단을 의미하기도 했습니다. 더 큰 문제는 JSON 같은 복잡한 중첩 구조를 저장하려면 여러 테이블로 쪼개야 한다는 점이었습니다.
프로젝트가 커질수록 JOIN 연산이 늘어나고 성능이 저하되었습니다. 바로 이런 문제를 해결하기 위해 MongoDB가 등장했습니다.
MongoDB를 사용하면 스키마 유연성이 가능해집니다. 각 문서마다 다른 필드를 가질 수 있어 빠르게 변화하는 요구사항에 대응할 수 있습니다.
또한 중첩 구조 저장도 얻을 수 있습니다. JSON 형태로 데이터를 저장하므로 복잡한 객체 구조를 그대로 저장할 수 있습니다.
무엇보다 수평적 확장이라는 큰 이점이 있습니다. 위의 예시를 자세히 살펴보겠습니다.
먼저 _id 필드를 보면 MongoDB가 자동으로 생성하는 고유 식별자임을 알 수 있습니다. 이 부분이 관계형 데이터베이스의 Primary Key와 같은 역할을 합니다.
다음으로 specs 필드에서는 중첩된 객체 구조가 그대로 저장됩니다. tags는 배열로 저장되고, reviews는 객체의 배열로 저장됩니다.
별도의 테이블 없이 모든 정보가 하나의 문서에 담깁니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 SNS 서비스를 개발한다고 가정해봅시다. 사용자의 게시물, 댓글, 좋아요, 해시태그 등을 저장해야 합니다.
MongoDB를 활용하면 하나의 게시물 문서에 모든 관련 정보를 담을 수 있어 조회 성능이 크게 향상됩니다. 인스타그램, 페이스북 같은 많은 대형 서비스에서 이런 패턴을 적극적으로 사용하고 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 프로젝트에 MongoDB를 사용하려는 것입니다.
MongoDB는 유연성이 장점이지만, 복잡한 트랜잭션이 필요하거나 데이터 일관성이 매우 중요한 경우에는 관계형 데이터베이스가 더 적합할 수 있습니다. 따라서 프로젝트의 특성을 잘 파악하고 적절한 도구를 선택해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 많은 현대적인 서비스들이 MongoDB를 사용하는군요!" MongoDB의 기본 개념을 제대로 이해하면 더 유연하고 확장 가능한 애플리케이션을 설계할 수 있습니다. 여러분도 오늘 배운 내용을 바탕으로 프로젝트에 적합한 데이터베이스를 선택해 보세요.
실전 팁
💡 - MongoDB는 읽기 성능이 중요하고 데이터 구조가 자주 변경되는 경우에 적합합니다
- 금융 거래처럼 강력한 트랜잭션과 ACID가 필요한 경우에는 관계형 DB를 고려하세요
- 둘을 함께 사용하는 폴리글랏 퍼시스턴스 전략도 좋은 선택입니다
2. Spring Data MongoDB 설정
김개발 씨는 MongoDB를 사용하기로 결정했습니다. 하지만 어떻게 Spring Boot 프로젝트에 연결해야 할지 막막했습니다.
"설정이 복잡하지 않을까요?" 박시니어 씨가 웃으며 대답했습니다. "Spring Boot를 쓰면 생각보다 간단해요."
Spring Data MongoDB는 Spring 애플리케이션에서 MongoDB를 쉽게 사용할 수 있게 해주는 프레임워크입니다. 마치 JPA가 관계형 데이터베이스를 쉽게 다루게 해주는 것처럼, Spring Data MongoDB는 MongoDB를 위한 편리한 추상화를 제공합니다.
의존성 추가와 간단한 설정만으로 MongoDB를 사용할 준비가 완료됩니다.
다음 코드를 살펴봅시다.
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
// application.yml
spring:
data:
mongodb:
host: localhost
port: 27017
database: shopping-mall
# 인증이 필요한 경우
# username: admin
# password: secret
김개발 씨는 새로운 기술을 배울 때마다 설정의 복잡함에 고통받았던 기억이 있습니다. XML 설정 파일을 수십 줄 작성하고, 빈을 등록하고, 복잡한 연결 코드를 작성하던 시절 말입니다.
"MongoDB도 그럴까요?" 김개발 씨가 걱정스러운 표정으로 물었습니다. 박시니어 씨가 자신 있게 대답했습니다.
"Spring Boot의 자동 설정 덕분에 매우 간단해요." 그렇다면 Spring Data MongoDB는 정확히 무엇일까요? 쉽게 비유하자면, Spring Data MongoDB는 마치 통역사와 같습니다.
여러분이 한국어로 말하면 통역사가 영어로 번역해서 전달해주는 것처럼, 여러분이 Java 코드로 작성하면 Spring Data MongoDB가 MongoDB 명령어로 변환해서 실행해줍니다. 복잡한 MongoDB 쿼리 문법을 몰라도 자바 메서드 호출만으로 데이터베이스 작업을 수행할 수 있습니다.
Spring Data MongoDB가 없던 시절에는 어땠을까요? 개발자들은 MongoDB Java Driver를 직접 사용해야 했습니다.
연결 설정부터 시작해서 모든 쿼리를 직접 작성하고, 결과를 Java 객체로 변환하는 코드를 일일이 작성해야 했습니다. 코드가 길어지고 반복적이었으며, 실수하기도 쉬웠습니다.
더 큰 문제는 데이터베이스 연결 관리를 직접 해야 한다는 점이었습니다. 바로 이런 문제를 해결하기 위해 Spring Data MongoDB가 등장했습니다.
Spring Data MongoDB를 사용하면 자동 설정이 가능해집니다. Spring Boot가 클래스패스를 확인하고 자동으로 MongoDB 연결을 구성해줍니다.
또한 Repository 패턴도 얻을 수 있습니다. JPA Repository처럼 인터페이스만 선언하면 구현체를 자동으로 생성해줍니다.
무엇보다 템플릿 지원이라는 큰 이점이 있습니다. MongoTemplate을 통해 복잡한 쿼리도 쉽게 작성할 수 있습니다.
위의 설정 코드를 자세히 살펴보겠습니다. 먼저 build.gradle 파일에서 spring-boot-starter-data-mongodb 의존성을 추가합니다.
이 한 줄이 MongoDB 관련 모든 라이브러리를 가져옵니다. 다음으로 application.yml에서는 MongoDB 연결 정보를 설정합니다.
host는 MongoDB 서버 주소, port는 기본값인 27017, database는 사용할 데이터베이스 이름입니다. 인증이 필요한 경우 username과 password를 추가하면 됩니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 개발, 스테이징, 운영 환경이 분리된 프로젝트라고 가정해봅시다.
application-dev.yml, application-staging.yml, application-prod.yml로 환경별 설정을 분리하여 각 환경마다 다른 MongoDB 서버에 연결할 수 있습니다. 민감한 정보는 환경 변수로 관리하여 보안을 강화하는 것이 일반적입니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 application.yml에 운영 DB 비밀번호를 그대로 커밋하는 것입니다.
이렇게 하면 Git 저장소에 민감한 정보가 노출될 수 있습니다. 따라서 환경 변수나 외부 설정 파일을 활용하여 민감한 정보를 관리해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 설정을 완료한 김개발 씨는 감탄했습니다.
"정말 간단하네요! JPA 설정과 거의 비슷해요." Spring Data MongoDB의 설정을 제대로 이해하면 빠르게 프로젝트에 MongoDB를 도입할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 로컬 개발 시 Docker로 MongoDB를 띄우면 설치 없이 빠르게 시작할 수 있습니다
- MongoDB Atlas를 사용하면 클라우드 기반 MongoDB를 무료로 사용할 수 있습니다
- application.yml 대신 Java Config로도 설정 가능하지만, yml이 더 간결합니다
3. Document 클래스 작성
설정을 마친 김개발 씨는 이제 실제 데이터를 다룰 차례였습니다. "상품 정보를 어떻게 클래스로 만들어야 할까요?" JPA의 Entity를 떠올리며 고민하던 김개발 씨에게 박시니어 씨가 힌트를 줬습니다.
"거의 비슷해요. 다만 @Document 어노테이션을 사용하죠."
MongoDB의 Document 클래스는 JPA의 Entity와 유사한 역할을 합니다. @Document 어노테이션으로 MongoDB 컬렉션과 매핑되는 Java 클래스임을 표시하고, @Id로 식별자 필드를 지정합니다.
@Field 어노테이션으로 필드명을 커스터마이징할 수 있으며, 중첩된 객체나 리스트도 자유롭게 사용할 수 있습니다.
다음 코드를 살펴봅시다.
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.List;
@Document(collection = "products")
public class Product {
@Id
private String id;
private String name;
private Integer price;
private String category;
// 중첩 객체
private Specs specs;
// 배열 필드
private List<String> tags;
@Field("review_list") // MongoDB 필드명 커스터마이징
private List<Review> reviews;
// getter, setter, constructor 생략
}
class Specs {
private String color;
private String storage;
private String ram;
}
class Review {
private String user;
private Integer rating;
private String comment;
}
김개발 씨는 JPA를 사용하며 Entity 클래스를 작성한 경험이 있었습니다. @Entity, @Table, @Column 등의 어노테이션에 익숙했습니다.
하지만 MongoDB는 조금 다를 것 같아 긴장했습니다. "관계형 데이터베이스가 아닌데, 어떻게 클래스를 설계해야 할까?" 고민하던 김개발 씨에게 박시니어 씨가 말했습니다.
"생각보다 단순해요. 오히려 더 자유롭죠." 그렇다면 Document 클래스란 정확히 무엇일까요?
쉽게 비유하자면, Document 클래스는 마치 설계도와 같습니다. 건축가가 건물 설계도를 그리면 그대로 건물이 지어지는 것처럼, Document 클래스를 작성하면 그 구조대로 MongoDB에 데이터가 저장됩니다.
다만 관계형 데이터베이스의 엄격한 설계도와 달리, MongoDB의 설계도는 훨씬 유연합니다. 방 하나를 더 추가하거나 구조를 변경하기가 쉽습니다.
JPA만 사용하던 시절에는 어땠을까요? 개발자들은 모든 관계를 테이블로 표현해야 했습니다.
상품의 리뷰를 저장하려면 별도의 Review 테이블을 만들고 외래 키로 연결해야 했습니다. 상품의 스펙 정보도 마찬가지였습니다.
하나의 상품 정보를 조회하려면 여러 테이블을 JOIN해야 했고, 이는 성능 저하로 이어졌습니다. 더 큰 문제는 스키마 변경이 어렵다는 점이었습니다.
바로 이런 문제를 해결하기 위해 Document 클래스의 유연한 구조가 등장했습니다. Document 클래스를 사용하면 중첩 구조 표현이 가능해집니다.
Specs나 Review 같은 복잡한 객체를 별도 컬렉션 없이 하나의 문서에 포함시킬 수 있습니다. 또한 컬렉션 필드도 얻을 수 있습니다.
List나 Set 같은 컬렉션을 필드로 직접 사용할 수 있어 배열 데이터를 쉽게 다룰 수 있습니다. 무엇보다 스키마 유연성이라는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 @Document(collection = "products") 부분을 보면 이 클래스가 products 컬렉션과 매핑됨을 알 수 있습니다.
이 부분이 JPA의 @Table 어노테이션과 유사한 역할을 합니다. 다음으로 @Id 어노테이션에서는 MongoDB의 _id 필드와 매핑되는 식별자를 지정합니다.
Specs specs 필드는 중첩된 객체로 저장되며, List<Review> reviews는 객체 배열로 저장됩니다. **@Field("review_list")**를 사용하면 Java 필드명과 MongoDB 필드명을 다르게 지정할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 전자상거래 플랫폼을 개발한다고 가정해봅시다.
상품마다 속성이 천차만별입니다. 의류는 사이즈 차트가 필요하고, 전자제품은 상세 스펙이 필요하며, 식품은 영양 정보가 필요합니다.
Document 클래스의 유연한 구조를 활용하면 각 카테고리별로 다른 필드를 가진 상품을 하나의 컬렉션에 저장할 수 있습니다. 쿠팡, 네이버 쇼핑 같은 대형 플랫폼에서 이런 패턴을 사용합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 연관 데이터를 하나의 문서에 넣으려는 것입니다.
예를 들어 주문 정보에 고객의 모든 정보를 통째로 넣으면 데이터 중복이 발생하고 일관성 문제가 생길 수 있습니다. 따라서 참조가 필요한 경우와 임베딩이 적절한 경우를 잘 구분해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. Document 클래스를 작성한 김개발 씨는 뿌듯해했습니다.
"JPA Entity보다 훨씬 간단하네요!" Document 클래스를 제대로 이해하면 MongoDB의 유연성을 최대한 활용할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - @Id 필드의 타입은 보통 String을 사용하며, MongoDB가 자동으로 ObjectId를 생성합니다
- 중첩이 3단계 이상 깊어지면 별도 컬렉션으로 분리하는 것을 고려하세요
- Lombok의 @Data, @Builder를 활용하면 boilerplate 코드를 줄일 수 있습니다
4. MongoRepository 사용
Document 클래스를 만든 김개발 씨는 이제 데이터베이스 작업을 수행할 차례였습니다. "쿼리를 어떻게 작성해야 하죠?" 걱정하던 김개발 씨에게 박시니어 씨가 놀라운 사실을 알려줬습니다.
"JpaRepository처럼 인터페이스만 만들면 끝이에요."
MongoRepository는 Spring Data MongoDB가 제공하는 Repository 인터페이스입니다. JpaRepository와 동일한 방식으로 사용할 수 있으며, 인터페이스를 선언하면 Spring이 자동으로 구현체를 생성해줍니다.
메서드 이름으로 쿼리를 자동 생성하는 Query Methods 기능을 지원하며, save, findById, delete 같은 기본 CRUD 메서드를 제공합니다.
다음 코드를 살펴봅시다.
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import java.util.List;
public interface ProductRepository extends MongoRepository<Product, String> {
// 메서드 이름으로 쿼리 자동 생성
List<Product> findByCategory(String category);
// 가격 범위 조회
List<Product> findByPriceBetween(Integer minPrice, Integer maxPrice);
// 이름에 키워드 포함 조회
List<Product> findByNameContaining(String keyword);
// 여러 조건 조합
List<Product> findByCategoryAndPriceLessThan(String category, Integer price);
// @Query 어노테이션으로 직접 쿼리 작성
@Query("{ 'tags': ?0 }")
List<Product> findByTag(String tag);
}
김개발 씨는 JPA를 사용하며 Repository 패턴의 편리함을 경험한 적이 있었습니다. 인터페이스만 선언하면 Spring이 알아서 구현체를 만들어주는 마법 같은 기능 말입니다.
"MongoDB도 똑같이 되나요?" 김개발 씨가 반신반의하며 물었습니다. 박시니어 씨가 자신 있게 고개를 끄덕였습니다.
"Spring Data의 핵심 철학이 일관성이거든요." 그렇다면 MongoRepository는 정확히 무엇일까요? 쉽게 비유하자면, MongoRepository는 마치 만능 비서와 같습니다.
여러분이 "카테고리가 스마트폰인 상품을 찾아줘"라고 말하면 비서가 알아서 데이터베이스를 뒤져서 결과를 가져옵니다. 복잡한 MongoDB 쿼리 문법을 몰라도, 그저 메서드 이름을 규칙에 맞게 작성하기만 하면 됩니다.
비서가 알아서 의도를 파악하고 적절한 작업을 수행합니다. Repository 패턴이 없던 시절에는 어땠을까요?
개발자들은 모든 데이터베이스 작업을 직접 구현해야 했습니다. DAO 클래스를 만들고, 각 메서드마다 MongoDB 쿼리를 직접 작성하고, 결과를 객체로 변환하는 코드를 일일이 작성해야 했습니다.
코드가 반복적이고 지루했으며, 실수하기도 쉬웠습니다. 더 큰 문제는 비즈니스 로직과 데이터 접근 로직이 뒤섞여 유지보수가 어려웠다는 점입니다.
바로 이런 문제를 해결하기 위해 MongoRepository가 등장했습니다. MongoRepository를 사용하면 자동 쿼리 생성이 가능해집니다.
findByCategory처럼 메서드 이름만으로 적절한 쿼리가 자동으로 만들어집니다. 또한 공통 메서드 제공도 얻을 수 있습니다.
save, findById, delete, count 같은 기본 CRUD 메서드를 별도 구현 없이 바로 사용할 수 있습니다. 무엇보다 타입 안정성이라는 큰 이점이 있습니다.
컴파일 타임에 메서드 시그니처를 체크하므로 런타임 오류를 줄일 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 MongoRepository<Product, String> 부분을 보면 Product 엔티티와 String 타입의 ID를 다루는 Repository임을 알 수 있습니다. 이 부분이 제네릭으로 타입을 명시하는 핵심입니다.
다음으로 findByCategory 메서드에서는 Category 필드가 일치하는 데이터를 찾는 쿼리가 자동 생성됩니다. findByPriceBetween은 Between 키워드로 범위 조회를 수행하고, findByNameContaining은 Containing 키워드로 부분 일치 검색을 수행합니다.
@Query 어노테이션을 사용하면 MongoDB의 JSON 쿼리를 직접 작성할 수도 있습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 쇼핑몰의 상품 검색 기능을 개발한다고 가정해봅시다. 사용자가 카테고리를 선택하고, 가격 범위를 지정하고, 키워드를 입력하면 그에 맞는 상품을 찾아야 합니다.
MongoRepository의 Query Methods를 활용하면 findByCategoryAndPriceBetweenAndNameContaining 같은 복잡한 조건도 메서드 이름만으로 쉽게 구현할 수 있습니다. 코드가 간결하고 의도가 명확해집니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 메서드 이름이 너무 길어지도록 방치하는 것입니다.
findByCategoryAndPriceBetweenAndNameContainingAndTagsContainingAndSpecsColorEquals 같은 메서드는 읽기도 어렵고 유지보수도 힘듭니다. 이렇게 복잡한 경우에는 @Query 어노테이션이나 QueryDSL을 사용하는 것이 더 적절합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. Repository를 작성한 김개발 씨는 신기해했습니다.
"정말 구현 안 해도 되네요! JPA랑 똑같아요!" MongoRepository를 제대로 이해하면 반복적인 데이터 접근 코드를 크게 줄일 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - Query Methods의 키워드는 공식 문서를 참고하세요 (Between, LessThan, GreaterThan, Containing 등)
- 복잡한 쿼리는 @Query 어노테이션 또는 MongoTemplate을 사용하는 것이 좋습니다
- Pageable을 파라미터로 받으면 페이징 처리가 자동으로 됩니다
5. CRUD 작업 구현
Repository를 만든 김개발 씨는 이제 실제 비즈니스 로직을 작성할 차례였습니다. "Create, Read, Update, Delete를 어떻게 구현하죠?" 박시니어 씨가 Service 클래스를 보여주며 설명했습니다.
"Repository의 메서드를 호출하기만 하면 돼요."
CRUD 작업은 데이터베이스의 기본 연산입니다. MongoRepository가 제공하는 save 메서드로 생성과 수정을 모두 처리하고, findById와 findAll로 조회하며, deleteById로 삭제합니다.
Service 레이어에서 Repository를 주입받아 비즈니스 로직과 데이터 접근 로직을 분리하는 것이 일반적인 패턴입니다.
다음 코드를 살펴봅시다.
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
// Create - 상품 생성
public Product createProduct(Product product) {
return productRepository.save(product);
}
// Read - 단건 조회
public Product getProduct(String id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("상품을 찾을 수 없습니다"));
}
// Read - 전체 조회
public List<Product> getAllProducts() {
return productRepository.findAll();
}
// Update - 상품 수정
public Product updateProduct(String id, Product updatedProduct) {
Product product = getProduct(id);
product.setName(updatedProduct.getName());
product.setPrice(updatedProduct.getPrice());
return productRepository.save(product); // save가 update 역할도 함
}
// Delete - 상품 삭제
public void deleteProduct(String id) {
productRepository.deleteById(id);
}
// 검색 - 카테고리별 조회
public List<Product> searchByCategory(String category) {
return productRepository.findByCategory(category);
}
}
김개발 씨는 드디어 실제로 동작하는 기능을 만들 단계에 이르렀습니다. 상품을 등록하고, 조회하고, 수정하고, 삭제하는 기본적인 작업들 말입니다.
"이 부분은 복잡하겠죠?" 김개발 씨가 각오를 다지며 물었습니다. 박시니어 씨가 웃으며 코드를 보여줬습니다.
"Repository 메서드만 호출하면 끝이에요." 그렇다면 CRUD 작업이란 정확히 무엇일까요? 쉽게 비유하자면, CRUD는 마치 도서관 사서의 기본 업무와 같습니다.
새 책을 등록하고(Create), 독자가 원하는 책을 찾아주고(Read), 책 정보를 수정하고(Update), 낡은 책을 폐기하는(Delete) 것처럼, 데이터베이스도 이 네 가지 기본 작업을 수행합니다. 모든 애플리케이션의 핵심 기능이 이 네 가지 작업의 조합으로 이루어집니다.
복잡한 데이터 접근 코드를 직접 작성하던 시절에는 어땠을까요? 개발자들은 각 작업마다 MongoDB 쿼리를 직접 작성하고, 연결을 관리하고, 예외를 처리하고, 결과를 변환하는 코드를 반복적으로 작성해야 했습니다.
같은 패턴의 코드가 프로젝트 전체에 흩어져 있었고, 하나를 수정하면 다른 곳도 함께 수정해야 했습니다. 더 큰 문제는 비즈니스 로직이 데이터 접근 로직과 뒤섞여 코드의 의도를 파악하기 어려웠다는 점입니다.
바로 이런 문제를 해결하기 위해 Service 레이어 패턴이 등장했습니다. Service 레이어를 사용하면 관심사 분리가 가능해집니다.
비즈니스 로직은 Service에, 데이터 접근은 Repository에 두어 각자의 역할이 명확해집니다. 또한 재사용성 향상도 얻을 수 있습니다.
여러 Controller에서 같은 Service 메서드를 호출할 수 있습니다. 무엇보다 테스트 용이성이라는 큰 이점이 있습니다.
Repository를 Mock으로 대체하여 Service 로직만 독립적으로 테스트할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 @RequiredArgsConstructor 어노테이션을 보면 Lombok이 final 필드를 위한 생성자를 자동으로 만들어줌을 알 수 있습니다. 이 부분이 의존성 주입을 간단하게 해줍니다.
다음으로 createProduct 메서드에서는 save 메서드 하나로 생성 작업이 완료됩니다. getProduct은 findById로 조회하고 Optional을 처리합니다.
updateProduct는 먼저 기존 데이터를 조회한 후 값을 변경하고 save로 저장하는데, MongoDB는 ID가 같으면 자동으로 업데이트를 수행합니다. deleteProduct는 deleteById로 간단히 삭제합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 전자상거래 API를 개발한다고 가정해봅시다.
관리자는 상품을 등록하고 수정하고 삭제할 수 있어야 하며, 일반 사용자는 상품을 조회하고 검색할 수 있어야 합니다. Service 레이어에서 createProduct 같은 메서드는 권한 검증 로직을 추가하고, searchByCategory는 캐싱을 적용하여 성능을 개선할 수 있습니다.
비즈니스 요구사항이 복잡해져도 Service 레이어만 수정하면 됩니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 Service 메서드가 Repository 메서드를 단순히 호출만 하는 것입니다. 만약 추가 로직이 전혀 없다면 Controller에서 Repository를 직접 사용하는 것도 고려할 수 있습니다.
하지만 대부분의 경우 검증, 변환, 권한 체크 등의 로직이 필요하므로 Service 레이어를 두는 것이 장기적으로 유리합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
CRUD 작업을 완성한 김개발 씨는 뿌듯해했습니다. "생각보다 간단하네요!
이제 API만 만들면 되겠어요." CRUD 작업을 제대로 이해하면 대부분의 비즈니스 요구사항을 구현할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - save 메서드는 ID가 없으면 Insert, 있으면 Update를 수행합니다
- findById는 Optional을 반환하므로 orElseThrow로 예외 처리를 명확히 하세요
- 대량 삭제가 필요한 경우 deleteAllById 또는 deleteAll을 사용하세요
6. JPA vs MongoDB 비교
모든 기능을 완성한 김개발 씨가 박시니어 씨에게 물었습니다. "그런데 선배님, JPA와 MongoDB 중에 뭘 써야 할지 어떻게 판단하나요?" 박시니어 씨가 커피를 한 모금 마시고 천천히 설명하기 시작했습니다.
"각각 장단점이 있어요. 프로젝트 특성에 따라 선택해야 하죠."
JPA는 관계형 데이터베이스를 위한 ORM으로 강력한 트랜잭션과 ACID를 보장하며, MongoDB는 NoSQL 데이터베이스로 유연한 스키마와 수평적 확장성이 장점입니다. JPA는 복잡한 관계와 일관성이 중요한 경우에 적합하고, MongoDB는 대용량 데이터와 빠른 변화가 필요한 경우에 적합합니다.
두 기술을 함께 사용하는 폴리글랏 퍼시스턴스도 좋은 선택입니다.
다음 코드를 살펴봅시다.
// JPA Entity 예시
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user; // 외래 키 관계
private LocalDateTime orderDate;
private Integer totalAmount;
}
// MongoDB Document 예시
@Document(collection = "orders")
public class Order {
@Id
private String id;
// 관계 대신 임베딩
private User user; // 중첩 객체로 저장
private LocalDateTime orderDate;
private Integer totalAmount;
private List<OrderItem> items; // 배열로 저장
}
김개발 씨는 이제 MongoDB를 자유롭게 다룰 수 있게 되었습니다. 하지만 한 가지 의문이 생겼습니다.
기존에 배웠던 JPA와 MongoDB 중 무엇을 선택해야 하는지 명확한 기준이 필요했습니다. "프로젝트마다 다르다는 건 알겠는데, 구체적으로 어떤 기준으로 선택하죠?" 김개발 씨의 질문에 박시니어 씨가 진지하게 대답했습니다.
"좋은 질문이에요. 이건 아키텍트의 핵심 역량이죠." 그렇다면 JPA와 MongoDB의 차이점은 정확히 무엇일까요?
쉽게 비유하자면, JPA는 엄격한 규칙이 있는 은행과 같습니다. 모든 거래가 정확히 기록되고, 잔액이 맞아야 하며, 규칙을 어기면 거래가 취소됩니다.
반면 MongoDB는 유연한 개인 금고와 같습니다. 어떤 형태의 물건이든 넣을 수 있고, 필요에 따라 칸막이를 조정할 수 있으며, 빠르게 물건을 꺼낼 수 있습니다.
각각의 용도가 다릅니다. 모든 프로젝트에 관계형 데이터베이스를 사용하던 시절에는 어땠을까요?
개발자들은 유연성이 필요한 경우에도 어쩔 수 없이 테이블 구조로 데이터를 억지로 맞춰야 했습니다. SNS의 피드처럼 빠르게 변하는 데이터도 정규화된 테이블에 저장하다 보니 성능 문제가 발생했습니다.
반대로 NoSQL만 고집하던 개발자들은 금융 거래 같은 일관성이 중요한 데이터까지 MongoDB에 저장하려다 문제를 겪기도 했습니다. 적재적소에 맞는 도구를 선택하는 것이 중요합니다.
바로 이런 문제를 해결하기 위해 폴리글랏 퍼시스턴스 전략이 등장했습니다. JPA를 사용하면 강력한 트랜잭션이 가능해집니다.
주문, 결제, 재고 관리처럼 정확성이 생명인 영역에 적합합니다. 또한 복잡한 관계 표현도 얻을 수 있습니다.
외래 키, 조인, 제약조건으로 데이터 무결성을 보장합니다. 반면 MongoDB를 사용하면 빠른 개발 속도가 가능해집니다.
스키마 변경이 쉬워 빠르게 변하는 요구사항에 대응할 수 있습니다. 수평적 확장도 얻을 수 있습니다.
샤딩으로 대용량 데이터를 여러 서버에 분산 저장할 수 있습니다. 위의 코드를 비교해 보겠습니다.
먼저 JPA 버전을 보면 @ManyToOne으로 User와의 관계를 정의합니다. 이는 별도의 users 테이블이 있고 외래 키로 연결됨을 의미합니다.
User 정보를 조회하려면 조인이 필요합니다. 반면 MongoDB 버전에서는 User 객체를 그대로 임베딩합니다.
별도의 컬렉션 없이 하나의 문서에 모든 정보가 담깁니다. OrderItem 배열도 마찬가지로 임베딩되어 조회 성능이 향상됩니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 대형 이커머스 플랫폼을 개발한다고 가정해봅시다.
주문, 결제, 재고 같은 핵심 트랜잭션 영역은 JPA와 PostgreSQL을 사용하여 데이터 일관성을 보장합니다. 반면 상품 카탈로그, 리뷰, 검색 로그 같은 영역은 MongoDB를 사용하여 유연성과 성능을 확보합니다.
쿠팡, 배달의민족 같은 대형 서비스들이 이런 하이브리드 전략을 사용합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 "MongoDB가 최신 기술이니 무조건 써야지"라고 생각하는 것입니다. 기술은 도구일 뿐이며, 문제에 맞는 도구를 선택해야 합니다.
은행 시스템에 MongoDB를 쓰거나, 실시간 로그 수집에 JPA를 쓰는 것은 적절하지 않습니다. 따라서 데이터의 특성, 일관성 요구사항, 조회 패턴, 확장성 필요 여부 등을 종합적으로 고려해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 우리 프로젝트는 JPA와 MongoDB를 같이 쓰는군요!" JPA와 MongoDB의 특성을 제대로 이해하면 프로젝트에 적합한 선택을 할 수 있습니다. 여러분도 오늘 배운 내용을 바탕으로 현명한 기술 선택을 해보세요.
실전 팁
💡 - 트랜잭션이 중요한가? → JPA
- 스키마가 자주 변경되는가? → MongoDB
- 복잡한 조인이 많은가? → JPA
- 대용량 읽기가 많은가? → MongoDB
- 둘 다 필요하다면 폴리글랏 퍼시스턴스를 고려하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
관찰 가능한 마이크로서비스 완벽 가이드
마이크로서비스 환경에서 시스템의 상태를 실시간으로 관찰하고 모니터링하는 방법을 배웁니다. Resilience4j, Zipkin, Prometheus, Grafana, EFK 스택을 활용하여 안정적이고 관찰 가능한 시스템을 구축하는 실전 가이드입니다.
Prometheus 메트릭 수집 완벽 가이드
Spring Boot 애플리케이션의 메트릭을 Prometheus로 수집하고 모니터링하는 방법을 배웁니다. Actuator 설정부터 PromQL 쿼리까지 실무에 필요한 모든 내용을 다룹니다.
스프링 관찰 가능성 완벽 가이드
Spring Boot 3.x의 Observation API를 활용한 애플리케이션 모니터링과 추적 방법을 초급 개발자 눈높이에서 쉽게 설명합니다. 실무에서 바로 적용할 수 있는 메트릭 수집과 분산 추적 기법을 다룹니다.
Zipkin으로 추적 시각화 완벽 가이드
마이크로서비스 환경에서 분산 추적을 시각화하는 Zipkin의 핵심 개념과 활용 방법을 초급자도 쉽게 이해할 수 있도록 실무 스토리로 풀어낸 가이드입니다. Docker 실행부터 UI 분석까지 단계별로 배웁니다.
Micrometer Tracing 완벽 가이드
분산 시스템에서 요청 흐름을 추적하는 Micrometer Tracing의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 실무 스토리와 비유로 풀어낸 완벽 가이드입니다.