Capítulo 13: Persistência NoSQL com Spring Data MongoDB (@Document)
Especialização em Backend com Java & Spring Boot • Spring Boot 3 & Java 21 LTS • Spring Data JPA, Security, Microsserviços e Cloud
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
B --> C["Controller / Handler"]
C --> D["Service Layer (Regras de Negócio)"]
D --> E["Repository / ORM (Persistência)"]
E --> F["Banco de Dados / Cache"]
subgraph ARQ["Arquitetura do Capítulo"]
G["Conceito: Persistência NoSQL com Spring Data MongoDB (@Document)"]
H["Segurança, Validação e Resiliência"]
I["Alta Performance e Escalabilidade"]
end
D --> ARQ
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Fundamentos Técnicos de Persistência NoSQL com Spring Data MongoDB (@Document)
Documento vs. tabela relacional. Diferente do Spring Data JPA (Capítulo 06-08), onde uma entidade mapeia uma linha em uma tabela com esquema rígido e relacionamentos via Foreign Key, o Spring Data MongoDB mapeia uma classe/record para um documento BSON (JSON binário) dentro de uma coleção — o equivalente conceitual mais próximo de uma tabela, mas sem schema fixo: dois documentos na mesma coleção podem ter campos diferentes. A anotação @Document(collection = "logs_auditoria") marca a classe; @Id marca o identificador (por padrão mapeado para o campo _id do MongoDB, geralmente um ObjectId de 12 bytes gerado automaticamente quando o campo é String ou ObjectId e vem nulo na inserção); @Field("nome_alternativo") permite renomear a serialização de um atributo; @Indexed cria um índice na coleção para acelerar buscas por aquele campo, sem o qual o MongoDB faria um collection scan completo.
Documentos aninhados em vez de JOINs. O MongoDB não tem JOIN nativo eficiente (existe $lookup na agregação, mas é caro). O padrão idiomático é desnormalizar: em vez de duas coleções relacionadas por chave estrangeira, embutir o subdocumento diretamente (um Pedido que contém a lista de ItemPedido como um array de objetos aninhados dentro do próprio documento). Isso favorece leituras (um único acesso ao disco traz tudo) ao custo de duplicação de dados e maior complexidade em atualizações que afetam múltiplos documentos-pai.
MongoRepository. Estende a mesma família de abstrações do Spring Data usada no JPA — query methods funcionam de forma idêntica na convenção de nomes (findByUsuarioId, findByCategoriaAndPrecoLessThan), mas o Spring Data MongoDB os traduz para filtros de documento BSON, não para SQL. Quando a convenção de nome não é suficiente, a anotação @Query("{ 'categoria': ?0, 'preco': { $lte: ?1 } }") aceita a sintaxe nativa de operadores do MongoDB, com ?0, ?1 como placeholders posicionais para os argumentos do método.
MongoTemplate e o Aggregation Framework. Para consultas analíticas complexas — agrupamentos, contagens, pipelines de transformação — o MongoTemplate expõe a API programática do Aggregation Framework, que monta um pipeline de estágios ($match filtra documentos, $group agrupa e agrega valores, $sort, $project remodela a saída, $count) executado inteiramente dentro do servidor MongoDB, evitando trazer dados brutos para a JVM só para sumarizar em Java.
Transações multi-documento. Historicamente o MongoDB garantia atomicidade apenas em nível de um único documento. Desde a versão 4.0 (com Replica Set) e 4.2 (com Sharded Cluster), transações ACID multi-documento são suportadas via MongoTransactionManager, permitindo @Transactional em operações que precisam alterar múltiplos documentos/coleções atomicamente — mas o uso deve ser criterioso: transações multi-documento têm custo de performance maior que no modelo relacional, e frequentemente um bom modelo de dados desnormalizado (documento aninhado) elimina a necessidade delas.
Quando escolher MongoDB. Cenários com esquema variável entre registros (catálogos de produto com atributos que mudam por categoria), alto volume de escrita/ingestão (logs, eventos, telemetria) e necessidade de escala horizontal por sharding favorecem o modelo de documentos. Sistemas que exigem integridade referencial estrita entre múltiplas entidades e transações financeiras complexas com forte normalização (3NF) continuam mais bem servidos por um RDBMS como PostgreSQL.
💻 2. Código de Demonstração Corporativo
package com.empresa.api.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.time.Instant;
import java.util.List;
@Document(collection = "logs_auditoria")
public record LogAuditoria(
@Id String id,
@Indexed String usuarioId,
String acao,
@Field("detalhes_extras") List<String> detalhes,
@Indexed Instant timestamp
) {}
package com.empresa.api.repository;
import com.empresa.api.model.LogAuditoria;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import java.time.Instant;
import java.util.List;
public interface LogAuditoriaRepository extends MongoRepository<LogAuditoria, String> {
// Query method: traduzido automaticamente para filtro BSON { usuarioId: ?, timestamp: { $gte: ?, $lte: ? } }
List<LogAuditoria> findByUsuarioIdAndTimestampBetween(String usuarioId, Instant inicio, Instant fim);
// Filtro nativo MongoDB quando a convenção de nome não é expressiva o suficiente.
@Query("{ 'acao': { $in: ?0 } }")
List<LogAuditoria> buscarPorAcoes(List<String> acoes);
}
package com.empresa.api.service;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MongoAggregationService {
private final MongoTemplate mongoTemplate;
public MongoAggregationService(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
public List<Document> totalDeAcessosPorUsuario() {
Aggregation pipeline = Aggregation.newAggregation(
Aggregation.group("usuarioId").count().as("total"),
Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "total")
);
AggregationResults<Document> resultado =
mongoTemplate.aggregate(pipeline, "logs_auditoria", Document.class);
return resultado.getMappedResults();
}
}