본문 바로가기
Develop Study/Spring 2025. 2. 18.

(25.02.18) Spring Boot 의 MongoDB 연결

모 스타트업 회사에 인턴쉽에 참여하게 되면서 엔터티와 테이블기반의 RDMBS를 활용하는 MySQL을 사용했지만,

AI 학습용 데이터 및 대용량 데이터를 다루는 NoSQL기반의 DB를 활용을 해야했기 때문에,

MySQL기반과 다르게 Spring Boot 와 NoSQL인 MongoDB를 활용하는 방법을 학습하면서 기록했다.


MongoDB

  • NoSQL 기반의 데이터베이스 관계형 데이터베이스(RDBMS)와는 다른 구조로 데이터를 저장
  • 문서 지향(Document-Oriented) 형식
    • 데이터를 문서 단위로 저장
    • BSON(Binary JSON) 형식으로 저장
      • MongoDB에서 지원하는 JSON (JavaScript Object Notation)을 기반 이진 형식(Binary format)으로 저장되는 형식
      • JSON 과 똑같이 생겼지만, 필드와 필드 값을 key-value 형태로 저장하는 방식 : JSON 째로 저장할 경우 파일자체가 너무 커지기 때문에 Binary Data 형태로 저장

MongoDB의 주요 특징

  • 스키마 유용성
    • 데이터를 삽입할 때 특정 컬럼이나 테이블을 맞추거나 지정할 필요 없이 MongoDB는 스키마가 유연하게 활용할 수 있음
      • ex) 사용자와 상품 데이터를 하나의 컬렉션(Collection)에 저장해도 문제 X : 사실상 다른 Table로 설정을 해야하지만 그럴 필요 없음
    • 각가의 데이터는 기본적으로 ObjectId 사용 하여 저장
      • MongoDB는 각 문서에 대해 ObjectId를 자동으로 생성하여 _id 필드에 저장
      • 12바이트 길이의 이진 데이터로 구성되며, 이를 24자리의 16진수 문자열로 표현
// 사용자 데이터 (User)
{
    "_id": "user1",
    "name": "John",
    "email": "john@example.com",
    "age": 30
}

// 상품 데이터 (Product)
{
    "_id": "product1",
    "name": "Laptop",
    "price": 1200,
    "brand": "BrandX"
}
  • 확장성(Scalability): 분산형 Architecture
    • 수평 확장을 지원하여 데이터를 여러 서버에 분산 저장가능 → 성능 향상, 문서 중심이기 때문에
      • 자동으로 Sharding을 지원해서 데이터가 늘어나면 자동으로 분산 가능
        • 빠른 읽기/쓰기가 가능하며, 대용량 데이터 처리에 적합합니다.
        • ex) 쇼핑몰에서 상품 데이터를 빠르게 조회하거나 업데이트
  • 다양한 인덱스 옵션:
    • 인덱스(쿼리)를 사용해 검색 성능을 최적화
      • 특정 필드나 조건을 기준으로 문서를 검색을 가능
    • 단일 인덱스
      • find: 특정 조건에 맞는 데이터를 빠르게 검색할 수 있습니다.
      • sort: 데이터를 효율적으로 정렬할 수 있습니다.
      • filter: 특정 조건을 기준으로 데이터를 빠르게 필터링할 수 있습니다.
    • 복합 인덱스
      • 단일 인덱스 결합
// **name**은 오름차순(1), **age**는 내림차순(-1)으로 인덱스를 생성
db.users.createIndex({ "name": 1, "age": -1 });
  • 복잡한 Query 대응 가능
    • Aggregation Framework를 통해 복잡한 데이터 집계 및 필터링 작업 가능

Aggregation Framework

  • SQL의 GROUP BYHAVINGJOIN 등과 유사한 기능을 MongoDB 에서 활용할 수 있도록 하게 하는 복잡한 데이터 처리 및 변환을 할 수 있게 하는 일종의 Framework
  • Aggregation Pipeline 기반
    • 여러 개의 Stage로 구성되며, 각각 입력 데이터를 변형하거나 필터링 → 파이프라인을 통해 데이터를 단계적으로 처리
  • Stage
    • 데이터를 변형하는 작업 : 문서(document)를 받아서 처리한 후, 결과를 다음 단계로 순차적으로 전달
      • $match: 조건에 맞는 데이터를 필터 (SQL의 WHERE와 유사)
      • $group: 데이터를 그룹화하여 집계 (SQL의 GROUP BY와 유사)
      • $sort: 데이터를 정렬
      • $project: 필요한 필드를 선택하거나 새로운 필드를 계산
      • $limit: 결과의 개수를 제한
      • $skip: 특정 개수만큼 데이터를 건너뛰기
      • $unwind: 배열을 분해하여 각 요소를 개별 문서로 변환
      • $lookup: 다른 컬렉션과 조인(join)하는 데 사용
  • example: 특정 카테고리 내 상품을 가격대별로 묶어 정렬하는 작업
// 예시 문서: products 컬렉션
{
    "_id": "product1",
    "name": "Laptop",
    "price": 1200,
    "category": "Electronics"
},
{
    "_id": "product2",
    "name": "Phone",
    "price": 800,
    "category": "Electronics"
},
{
    "_id": "product3",
    "name": "Shoes",
    "price": 150,
    "category": "Fashion"
}
import org.springframework.data.mongodb.core.Aggregation;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;
import java.util.List;

// Spring Application 기준 Aggregation Framework 를 활용한 작업
@Service
public class ProductService {

    private final MongoTemplate mongoTemplate;

    public ProductService(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    public List<Object> getPriceBuckets() {
        // Aggregation Pipeline 생성
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("category").is("Electronics")), // 카테고리 필터링
            Aggregation.bucket("price",  // 가격대별로 묶기
                Aggregation.BucketBoundaries.builder().bucket(0, 500).bucket(500, 1000).bucket(1000, 1500).build(),
                Aggregation.BucketBuilder::count
            ),
            Aggregation.sort(Aggregation.sort(Direction.ASC, "_id")) // 가격대별로 정렬
        );

        // Aggregation 실행
        return mongoTemplate.aggregate(aggregation, Product.class, Object.class).getMappedResults();
    }
}

@Aggregation 을 사용하는 예제 (가장 많이 사용)

// 예시 데이터
[
  {
    "email": "alice@example.com",
    "name": "Alice",
    "purchases": [
      { "productId": "67b2cb63e6a9334bbaeb569e", "quantity": 3 },
      { "productId": "67b2cb63e6a9334bbaeb56a0", "quantity": 4 }
    ]
  },
  {
    "email": "bob@example.com",
    "name": "Bob",
    "purchases": [
      { "productId": "67b2cb63e6a9334bbaeb569e", "quantity": 10 },
      { "productId": "67b2cb63e6a9334bbaeb56a3", "quantity": 2 }
    ]
  },
  {
    "email": "charlie@example.com",
    "name": "Charlie",
    "purchases": [
      { "productId": "67b2cb63e6a9334bbaeb569e", "quantity": 5 }
    ]
  }
]

public interface UserRepository extends MongoRepository<User, String> {
    
    @Aggregation(pipeline = {
            "{ '$match': { 'purchases': { '$not': { '$elemMatch': { 'quantity': { '$gte': ?0 } } } } } }"
    })
    List<User> findUserWithPurchasesUnderMax(int maxQuantity);
}

  • @Query 처럼 사용을 하면서 SQL대신 Stage를 활용해서 사용할 수 있음
{ 
  "$match": { 
    "purchases": { 
      "$not": { 
        "$elemMatch": { 
          "quantity": { "$gte": ?0 } 
        } 
      } 
    } 
  } 
}
  • Stage
    • $elemMath : 배열 내부 요소 중 조건을 만족하는 항목 검사
    • $not : $elemMatch 조건을 만족하는 문서를 제외
    • $gte : ?0 : 특정 값보다 미만 항목이 있는지 확인
      • $gt 특정 값 미만
  • 동적으로 변수를 받아오는 부분에 대해서는 SpEL을 사용 → 위에서는 ?0 : 0번 인덱스 파라미터
    • SpEL
      • Spring Expression Language (SpEL)은 Spring에서 제공하는 표현 언어로, 문자열, 변수, 메서드 호출, 컬렉션 조작, 조건문 등 다양한 표현식을 평가하고 실행할 수 있는 기능

Spring Boot 에서의 MongoDB

  1. Gradle 의존성 추가
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}

단, 'org.springframework.boot:spring-boot-starter-data-jpa' 과 중첩이 되어서 의존성 주입이 되어있으면 충돌이 나버림

 

2. application.yml 설정

spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/{DB이름}
spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: {데이터베이스 이름}
      username: {이름}
      password: {비밀번호}
      authentication-database: admin
  • 비밀번호가 있는경우 설정이 가능

3. MongoDB Entity

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "users")
public class User {
    @Id
    private String id;
    private String name;
    private int age;
}

  • @Document: 이 클래스가 특정 MongoDB의 컬렉션(위에서는 users, MySQL 에서의 table = “users”)에
  • @Id: MongoDB에서 이 필드가 문서의 고유 식별자
  • 각각의 엔터티 역할을 하는 Documnet는 관계가 없기 때문에 @OneToMany 등 관계를 정의를 할 필요가 없음

4. MongoRepository

import org.springframework.data.mongodb.repository.MongoRepository;

public interface UserRepository extends MongoRepository<User, String> {
    User findByName(String name);
}
  • MongoDB와 데이터를 연결하려면 Spring Data MongoDB에서 제공하는 MongoRepository를 구현하는 interface로 구성 가능
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;

    public User createUser(User user) {
        return userRepository.save(user);
    }

    public User getUserByName(String name) {
        return userRepository.findByName(name);
    }
}

  • 위와 같이 똑같이 비지니스 로직에서도 똑같이 사용이 가능

 


그 외에는 Spring Boot에서 DB응 활용하는 디자인패턴은 일정하기 때문에

똑같이 사용할 수 있을 것이다.