본문으로 건너뛰기
김신건의 로그

[Spring Data] MongoDB: MongoTemplate, MongoRepository

· 수정 · 📖 약 2분 · 663자/단어 #spring #spring-data #mongodb #nosql #document-db
Spring Data MongoDB, MongoTemplate, MongoRepository, ReactiveMongoTemplate, @Document, spring mongo, 스프링 몽고

정의

Spring Data MongoDB 는 MongoDB 를 Spring 스타일로 사용하는 추상화. JPA 와 비슷한 Repository 패턴 + MongoDB native query (BSON, aggregation) 둘 다 지원.

특징:

  • Document 기반: row 가 아닌 nested JSON 문서. flexible schema
  • JPA 와 호환성 의도적 유사: @Document, MongoRepository, findBy* query method
  • Reactive 지원: ReactiveMongoTemplate, ReactiveMongoRepository (WebFlux 와 통합)

의존성

implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
// 또는 reactive
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/myapp
      # 또는 분리
      host: localhost
      port: 27017
      database: myapp
      username: admin
      password: ${MONGO_PASSWORD}
      auto-index-creation: true   # @Indexed 어노테이션 자동 처리

Entity 매핑

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;

@Document(collection = "posts")
@CompoundIndexes({
    @CompoundIndex(name = "author_created", def = "{'authorId': 1, 'createdAt': -1}")
})
public class Post {
    @Id
    private String id;       // ObjectId 또는 String

    @Indexed
    private String authorId;

    private String title;
    private String body;
    private List<String> tags;
    private Address location;   // nested document
    private Instant createdAt;

    // getter / setter / no-args ctor
}

public class Address {
    private String city;
    private String country;
}

@Id 가 없으면 Mongo 가 ObjectId 자동 생성. String 타입 권장 (Java 호환).

JPA 와 다른 점:

  • 별도 join 없음 (nested document 또는 reference)
  • schema 강제 안 함 (Mongo level)
  • @Embeddable 불필요 (nested 가 native)

MongoRepository

public interface PostRepository extends MongoRepository<Post, String> {
    List<Post> findByAuthorId(String authorId);
    List<Post> findByTagsContaining(String tag);
    Page<Post> findByCreatedAtAfter(Instant date, Pageable pageable);

    @Query("{ 'authorId': ?0, 'tags': { $in: ?1 } }")
    List<Post> findByAuthorAndTags(String authorId, List<String> tags);
}

JPA JpaRepository 와 거의 동일한 사용법. @Query 는 native MongoDB query (BSON).

@Service
class PostService {
    private final PostRepository repo;

    public List<Post> recent(String authorId) {
        return repo.findByAuthorId(authorId).stream()
            .sorted(Comparator.comparing(Post::getCreatedAt).reversed())
            .limit(10)
            .toList();
    }
}

MongoTemplate (저수준)

복잡한 query, aggregation, 동적 조건은 MongoTemplate 직접 사용:

@Service
class PostQueryService {
    private final MongoTemplate mongo;

    public List<Post> findByCriteria(String city, List<String> tags) {
        Query q = new Query();
        if (city != null) q.addCriteria(Criteria.where("location.city").is(city));
        if (tags != null && !tags.isEmpty()) q.addCriteria(Criteria.where("tags").in(tags));
        q.with(Sort.by(Sort.Direction.DESC, "createdAt")).limit(50);
        return mongo.find(q, Post.class);
    }
}

JPA Specification 보다 가독성 좋음.

Update 부분 ($set, $inc)

mongo.updateFirst(
    Query.query(Criteria.where("id").is(postId)),
    new Update().inc("viewCount", 1).set("lastViewedAt", Instant.now()),
    Post.class
);

JPA 의 dirty checking 과 다른 패턴. 명시적 update operator. 원자적이고 빠름.

Aggregation Pipeline

MongoDB 의 강력함:

java
Aggregation pipeline = Aggregation.newAggregation(
  Aggregation.match(Criteria.where("createdAt").gt(LocalDate.now().minusDays(30))),
  Aggregation.group("authorId")
      .count().as("postCount")
      .avg("viewCount").as("avgViews"),
  Aggregation.sort(Sort.Direction.DESC, "postCount"),
  Aggregation.limit(10)
);
AggregationResults<AuthorStats> results = mongo.aggregate(pipeline, "posts", AuthorStats.class);
results.forEach(System.out::println);
결과 row
AuthorStats{authorId='u1', postCount=42, avgViews=151.3}
AuthorStats{authorId='u2', postCount=38, avgViews=89.5}
...

GROUP BY + window function 을 declarative 하게. RDB 의 WITH ... GROUP BY ... ORDER BY ... LIMIT 에 대응.

인덱스

@Document(collection = "users")
public class User {
    @Id String id;

    @Indexed(unique = true)
    String email;

    @Indexed
    String username;

    @TextIndexed
    String bio;   // 텍스트 검색용
}

auto-index-creation = true 면 entity 스캔 후 인덱스 자동 생성. 운영 환경에선 끄고 migration 도구 (mongo-shell, mongock) 로 관리 권장.

TTL Index (자동 만료)

@Document(collection = "sessions")
public class Session {
    @Id String id;
    String userId;

    @Indexed(expireAfterSeconds = 3600)
    Instant createdAt;   // 1시간 후 자동 삭제
}

Mongo background job 이 주기적으로 삭제. 자동 cleanup 가능.

Transaction

MongoDB 4.0+ 부터 multi-document transaction 지원:

@Service
class TransferService {
    @Transactional
    public void transfer(String from, String to, BigDecimal amount) {
        accountRepo.decrement(from, amount);
        accountRepo.increment(to, amount);
        // 둘 다 성공해야 commit, 실패 시 rollback
    }
}

설정:

@Configuration
class MongoConfig extends AbstractMongoClientConfiguration {
    @Bean
    MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }
}

요구사항: replica set 또는 sharded cluster. standalone 은 transaction 불가.

Reactive (WebFlux 통합)

public interface PostRepository extends ReactiveMongoRepository<Post, String> {
    Flux<Post> findByAuthorId(String authorId);
    Mono<Post> findFirstByTitle(String title);
}

@RestController
class PostApi {
    @GetMapping("/posts/by-author/{id}")
    public Flux<Post> findByAuthor(@PathVariable String id) {
        return repo.findByAuthorId(id);
    }
}

WebFlux + reactive Mongo = 완전 non-blocking. 대량 동시성에 강함.

change stream (real-time)

MongoDB 의 oplog 기반 실시간 알림:

@Service
class PostWatcher {
    private final ReactiveMongoTemplate mongo;

    @PostConstruct
    public void watch() {
        mongo.changeStream(Post.class)
            .filter(Criteria.where("operationType").is("insert"))
            .listen()
            .subscribe(event -> {
                System.out.println("new post: " + event.getBody());
            });
    }
}

Kafka 없이 in-DB pub-sub. CDC (Change Data Capture) 대안.

JPA vs Mongo 비교

항목JPA / SQL DBSpring Data Mongo
데이터 모델정규화된 table + FK비정규화된 document
SchemaDDL 강제flexible (앱 단)에서 강제
JoinSQL JOINnested doc 또는 manual lookup
IndexDB 가 관리@Indexed 또는 migration
Transaction표준4.0+ replica set 필수
QueryJPQL / SpecificationsBSON / Aggregation
MigrationFlyway / Liquibasemongock
대량 동시 read인덱스 + 캐싱sharding
복잡 querySQL window functionAggregation pipeline

함정과 베스트 프랙티스

  • @Id 타입은 String (ObjectId) 권장: serialization / API 매핑 단순
  • nested document 사용 신중: 16MB document 제한, 너무 깊으면 query 어려움
  • reference 는 lazy 아님: @DBRef 는 별도 query 발생, JPA 의 LAZY 와 다름
  • 인덱스 운영 환경 자동 생성 금지: startup 시간, lock 위험. migration 도구 사용
  • transaction 은 replica set 필수: dev 에선 docker-compose 로 replica set 띄우기
  • Aggregation 의 메모리 한계: 100MB. allowDiskUse=true 또는 단계 분리
  • schema 검증 (Mongo 3.6+) 적극 활용: 데이터 일관성 보장
  • N+1 동일하게 발생: nested 안 쓰고 findById 반복하면 비슷한 함정

관련 위키

이 글의 용어 (5개)
[JPA] FetchType: LAZY vs EAGER, N+1, fetch joinspring
정의 FetchType 은 JPA 가 연관 entity 를 언제 로드할지 결정하는 hint. - : entity 로딩 시 즉시 함께 SELECT - : 실제 사용 시점 (prox…
[Spring] Cache: @Cacheable, @CacheEvict, Redisspring
정의 Spring Cache는 메서드 결과를 캐시하는 추상화. 한 줄로 메모이제이션. 백엔드는 Caffeine, Redis, Hazelcast, EhCache 등 자유롭게. 캐시…
[Spring] Spring Data JPA: Repository, Query Methodsspring
정의 Spring Data JPA는 JPA(Hibernate) 위에 Repository 추상화를 얹어 boilerplate를 제거. 인터페이스만 정의하면 Spring이 구현체 자…
[Spring] Transaction: @Transactional, propagation, isolationspring
정의 Spring 트랜잭션은 AOP 기반 선언형 트랜잭션 관리. 한 줄로 메서드 전체를 트랜잭션 범위로. JDBC, JPA, MongoDB 등 backend에 동일 API. 기본…
[Spring] WebFlux: Reactive, Mono, Fluxspring
정의 Spring WebFlux는 Project Reactor 기반의 비동기·non-blocking 웹 프레임워크. Spring MVC와 별개. Netty 기본 (Tomcat도 …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기