Spring Data MongoDB: Projeções e Agregações

Spring Data MongoDB: Projeções e Agregações

1. Visão geral

Spring Data MongoDB fornece abstrações simples de alto nível para a linguagem de consulta nativa do MongoDB. Neste artigo,we will explore the support for Projections and Aggregation framework.

Se você for novo neste tópico, consulte nosso artigo introdutórioIntroduction to Spring Data MongoDB.

2. Projeção

No MongoDB, as projeções são uma maneira de buscar apenas os campos obrigatórios de um documento em um banco de dados. Isso reduz a quantidade de dados que devem ser transferidos do servidor de banco de dados para o cliente e, portanto, aumenta o desempenho.

Com Spring Data MongDB, as projeções podem ser usadas comMongoTemplateeMongoRepository.

Antes de prosseguirmos, vamos dar uma olhada no modelo de dados que usaremos:

@Document
public class User {
    @Id
    private String id;
    private String name;
    private Integer age;

    // standard getters and setters
}

2.1. Projeções usandoMongoTemplate

Os métodosinclude() eexclude() na classeField são usados ​​para incluir e excluir campos, respectivamente:

Query query = new Query();
query.fields().include("name").exclude("id");
List john = mongoTemplate.find(query, User.class);

Esses métodos podem ser encadeados para incluir ou excluir vários campos. O campo marcado como@Id (_id no banco de dados) é sempre buscado, a menos que seja explicitamente excluído.

Os campos excluídos sãonull na instância da classe de modelo quando os registros são buscados com a projeção. No caso em que os campos são de um tipo primitivo ou de sua classe de wrapper, o valor dos campos excluídos são valores padrão dos tipos primitivos.

Por exemplo,String serianull,int /Integer seria0eboolean /Boolean seriafalse.

Assim, no exemplo acima, o camponame seriaJohn,id serianulleage seria0.

2.2. Projeções usandoMongoRepository

Ao usar MongoRepositories, a anotaçãofields de@Query pode ser definida no formato JSON:

@Query(value="{}", fields="{name : 1, _id : 0}")
List findNameAndExcludeId();

O resultado seria o mesmo que usar o MongoTemplate. Ovalue=”\{}” indica que não há filtros e, portanto, todos os documentos serão obtidos.

3. Aggregation

A agregação no MongoDB foi criada para processar dados e retornar resultados computados. Os dados são processados ​​em estágios e a saída de um estágio é fornecida como entrada para o próximo estágio. Essa capacidade de aplicar transformações e fazer cálculos em dados em estágios torna a agregação uma ferramenta muito poderosa para análise.

Spring Data MongoDB fornece uma abstração para consultas de agregação nativa usando as três classesAggregation que envolve uma consulta de agregação,AggregationOperation que envolve estágios de pipeline individuais eAggregationResults que é o contêiner do resultado produzido por agregação.

Para executar uma agregação, primeiro, crie pipelines de agregação usando os métodos construtores estáticos na classeAggregation, em seguida, crie uma instância deAggregation usando o métodonewAggregation() na classeAggregation e finalmente execute a agregação usandoMongoTemplate:

MatchOperation matchStage = Aggregation.match(new Criteria("foo").is("bar"));
ProjectionOperation projectStage = Aggregation.project("foo", "bar.baz");

Aggregation aggregation
  = Aggregation.newAggregation(matchStage, projectStage);

AggregationResults output
  = mongoTemplate.aggregate(aggregation, "foobar", OutType.class);

Observe queMatchOperation eProjectionOperation implementamAggregationOperation. Existem implementações semelhantes para outros pipelines de agregação. OutType é o modelo de dados para a saída esperada.

Agora, veremos alguns exemplos e suas explicações para cobrir os principais pipelines e operadores de agregação.

O conjunto de dados que usaremos neste artigo lista detalhes sobre todos os códigos postais nos Estados Unidos, que podem ser baixados deMongoDB repository.

Vejamos um exemplo de documento após importá-lo para uma coleção chamadazips no banco de dadostest.

{
    "_id" : "01001",
    "city" : "AGAWAM",
    "loc" : [
        -72.622739,
        42.070206
    ],
    "pop" : 15338,
    "state" : "MA"
}

Por questões de simplicidade e para tornar o código conciso, nos próximos trechos de código, assumiremos que todos os métodosstatic da classeAggregation são importados estaticamente.

3.1. Obtenha todos os estados com uma ordem de população superior a 10 milhões por população decrescente

Aqui teremos três pipelines:

  1. Etapa de$group somando a população de todos os códigos postais

  2. Estágio$match para filtrar estados com população acima de 10 milhões

  3. Etapa$sort para classificar todos os documentos em ordem decrescente de população

A saída esperada terá um campo_id como estado e um campostatePop com a população total do estado. Vamos criar um modelo de dados para isso e executar a agregação:

public class StatePoulation {

    @Id
    private String state;
    private Integer statePop;

    // standard getters and setters
}

A anotação@Id mapeará o campo_id da saída parastate no modelo:

GroupOperation groupByStateAndSumPop = group("state")
  .sum("pop").as("statePop");
MatchOperation filterStates = match(new Criteria("statePop").gt(10000000));
SortOperation sortByPopDesc = sort(new Sort(Direction.DESC, "statePop"));

Aggregation aggregation = newAggregation(
  groupByStateAndSumPop, filterStates, sortByPopDesc);
AggregationResults result = mongoTemplate.aggregate(
  aggregation, "zips", StatePopulation.class);

A classeAggregationResults implementaIterable e, portanto, podemos iterar sobre ela e imprimir os resultados.

Se o modelo de dados de saída não for conhecido, a classe MongoDB padrãoDocument pode ser usada.

3.2. Obtenha o menor estado pela população média da cidade

Para esse problema, precisaremos de quatro estágios:

  1. $group para somar a população total de cada cidade

  2. $group para calcular a população média de cada estado

  3. $sort estágio para ordenar os estados pela população média da cidade em ordem crescente

  4. $limit para obter o primeiro estado com a menor média de população da cidade

Embora não seja necessariamente necessário, usaremos um estágio$project adicional para reformatar o documento de acordo com o modelo de dados deStatePopulation.

GroupOperation sumTotalCityPop = group("state", "city")
  .sum("pop").as("cityPop");
GroupOperation averageStatePop = group("_id.state")
  .avg("cityPop").as("avgCityPop");
SortOperation sortByAvgPopAsc = sort(new Sort(Direction.ASC, "avgCityPop"));
LimitOperation limitToOnlyFirstDoc = limit(1);
ProjectionOperation projectToMatchModel = project()
  .andExpression("_id").as("state")
  .andExpression("avgCityPop").as("statePop");

Aggregation aggregation = newAggregation(
  sumTotalCityPop, averageStatePop, sortByAvgPopAsc,
  limitToOnlyFirstDoc, projectToMatchModel);

AggregationResults result = mongoTemplate
  .aggregate(aggregation, "zips", StatePopulation.class);
StatePopulation smallestState = result.getUniqueMappedResult();

Neste exemplo, já sabemos que haverá apenas um documento no resultado, pois limitamos o número de documentos de saída a 1 no último estágio. Como tal, podemos invocargetUniqueMappedResult() para obter a instânciaStatePopulation necessária.

Outra coisa a notar é que, em vez de confiar na anotação@Id para mapear_id para o estado, fizemos isso explicitamente no estágio de projeção.

3.3. Obtenha o estado com códigos postais máximos e mínimos

Para este exemplo, precisamos de três estágios:

  1. $group para contar o número de códigos postais para cada estado

  2. $sort para ordenar os estados pelo número de códigos postais

  3. $group para encontrar o estado com códigos postais máximos e mínimos usando os operadores$firste$last

GroupOperation sumZips = group("state").count().as("zipCount");
SortOperation sortByCount = sort(Direction.ASC, "zipCount");
GroupOperation groupFirstAndLast = group().first("_id").as("minZipState")
  .first("zipCount").as("minZipCount").last("_id").as("maxZipState")
  .last("zipCount").as("maxZipCount");

Aggregation aggregation = newAggregation(sumZips, sortByCount, groupFirstAndLast);

AggregationResults result = mongoTemplate
  .aggregate(aggregation, "zips", Document.class);
Document document= result.getUniqueMappedResult();

Aqui não usamos nenhum modelo, mas usamos oDocument já fornecido com o driver MongoDB.

4. Conclusão

Neste artigo, aprendemos como buscar campos especificados de um documento no MongoDB usando projeções no Spring Data MongoDB.

Também aprendemos sobre o suporte à estrutura de agregação do MongoDB no Spring Data. Cobrimos as principais fases de agregação - agrupar, projetar, classificar, limitar e combinar e analisamos alguns exemplos de suas aplicações práticas. O código-fonte completo éavailable over on GitHub.