Pipeline de agregação Java
Maxime Beugnet8 min read • Published Feb 01, 2022 • Updated Oct 01, 2024
Avalie esse Início rápido
- Atualizar para o Java 21
- Atualize o driver Java para 5.0.0
- Atualize
logback-classic
para 1.2.13
- Atualizar para o Java 17
- Atualize o driver Java para 4.11.1
- Atualize o mongodb-crypt para 1.8.0
- Atualize o driver Java para 4.2.2.
- Exemplo de criptografia no nível do campo do lado do cliente adicionado.
- Atualize o driver Java para 4.1.1.
- O registro do driver Java agora está ativado por meio da popular APIJ SLF4, portanto, adicionei logback no
pom.xml
e um arquivo de configuraçãologback.xml
.
O aggregation pipeline é uma framework para agregação de dados modelada sobre o conceito de pipelines de processamento de dados, assim como o "pipe" no Linux Shell. Os documentos entram em um pipeline em vários estágios que transforma os documentos em resultados agregados.
É a maneira mais poderosa de trabalhar com seus dados no MongoDB. Ele nos permitirá fazer queries avançadas, como agrupar documentos, manipular arrays, remodelar modelos de documentos etc.
Vamos ver como podemos coletar esse poder usando Java.
Usarei o mesmo repositório de sempre nesta série. Se você ainda não tiver uma cópia dele, poderá cloná-lo ou apenas atualizá-lo, caso já o tenha:
1 git clone https://github.com/mongodb-developer/java-quick-start
Se você não configurou seu cluster gratuito no MongoDB Atlas, agora é um ótimo momento para fazê-lo. Você tem todas as instruções nesta postagem do blog.
No Conjunto dedados de amostrado MongoDB no MongoDB Atlas, vamos explorar um pouco a coleção
zips
no banco de dadossample_training
.1 MongoDB Enterprise Cluster0-shard-0:PRIMARY> db.zips.find({city:"NEW YORK"}).limit(2).pretty() 2 { 3 "_id" : ObjectId("5c8eccc1caa187d17ca72f8a"), 4 "city" : "NEW YORK", 5 "zip" : "10001", 6 "loc" : { 7 "y" : 40.74838, 8 "x" : 73.996705 9 }, 10 "pop" : 18913, 11 "state" : "NY" 12 } 13 { 14 "_id" : ObjectId("5c8eccc1caa187d17ca72f8b"), 15 "city" : "NEW YORK", 16 "zip" : "10003", 17 "loc" : { 18 "y" : 40.731253, 19 "x" : 73.989223 20 }, 21 "pop" : 51224, 22 "state" : "NY" 23 }
Como você pode ver, temos um documento para cada código postal nos EUA e, para cada um, temos a população associada.
Para calcular a população de Nova York, eu teria que somar a população de cada código postal para obter a população de toda a cidade.
Vamos tentar encontrar as 3 maiores cidades do estado do Texas. Vamos projetar isso no papel primeiro.
- Não preciso trabalhar com a collection inteira. Preciso filtrar apenas as cidades do Texas.
- Uma vez feito isso, posso reagrupar todos os códigos postais de uma mesma cidade para obter a população total.
- Então, posso ordenar minhas cidades por ordem decrescente ou população.
- Finalmente, posso manter as primeiras 3 cidades da minha lista.
A maneira mais fácil de construir este pipeline no MongoDB é usar o construtor de pipeline de agregação que está disponível no MongoDB Compass ou no MongoDB Atlas na guia
Collections
.Após uma pequena refatoração de código, aqui está o que eu tenho:
1 /** 2 * find the 3 most densely populated cities in Texas. 3 * @param zips sample_training.zips collection from the MongoDB Sample Dataset in MongoDB Atlas. 4 */ 5 private static void threeMostPopulatedCitiesInTexas(MongoCollection<Document> zips) { 6 Bson match = match(eq("state", "TX")); 7 Bson group = group("$city", sum("totalPop", "$pop")); 8 Bson project = project(fields(excludeId(), include("totalPop"), computed("city", "$_id"))); 9 Bson sort = sort(descending("totalPop")); 10 Bson limit = limit(3); 11 12 List<Document> results = zips.aggregate(List.of(match, group, project, sort, limit)).into(new ArrayList<>()); 13 System.out.println("==> 3 most densely populated cities in Texas"); 14 results.forEach(printDocuments()); 15 }
O driver do MongoDB oferece muitos ajudantes para tornar o código fácil de escrever e ler.
Como você pode ver, resolvi esse problema com:
- Um estágio $project para renomear o campo
_id
emcity
para uma saída limpa (não obrigatório, mas sou elegante),
Esta é a saída que obtemos:
1 ==> 3 most densely populated cities in Texas 2 { 3 "totalPop": 2095918, 4 "city": "HOUSTON" 5 } 6 { 7 "totalPop": 940191, 8 "city": "DALLAS" 9 } 10 { 11 "totalPop": 811792, 12 "city": "SAN ANTONIO" 13 }
No MongoDB 4.2, há 30 diferentes agregação pipeline stages que você pode usar para manipular seus documentos. Se você quiser saber mais, recomendamos que siga este curso na MongoDB University: MongoDB Aggregation.
Desta vez, estou usando a collection
posts
no mesmo banco de dados.1 MongoDB Enterprise Cluster0-shard-0:PRIMARY> db.posts.findOne() 2 { 3 "_id" : ObjectId("50ab0f8bbcf1bfe2536dc3f9"), 4 "body" : "Amendment I\n<p>Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the Government for a redress of grievances.\n<p>\nAmendment II\n<p>\nA well regulated Militia, being necessary to the security of a free State, the right of the people to keep and bear Arms, shall not be infringed.\n<p>\nAmendment III\n<p>\nNo Soldier shall, in time of peace be quartered in any house, without the consent of the Owner, nor in time of war, but in a manner to be prescribed by law.\n<p>\nAmendment IV\n<p>\nThe right of the people to be secure in their persons, houses, papers, and effects, against unreasonable searches and seizures, shall not be violated, and no Warrants shall issue, but upon probable cause, supported by Oath or affirmation, and particularly describing the place to be searched, and the persons or things to be seized.\n<p>\nAmendment V\n<p>\nNo person shall be held to answer for a capital, or otherwise infamous crime, unless on a presentment or indictment of a Grand Jury, except in cases arising in the land or naval forces, or in the Militia, when in actual service in time of War or public danger; nor shall any person be subject for the same offence to be twice put in jeopardy of life or limb; nor shall be compelled in any criminal case to be a witness against himself, nor be deprived of life, liberty, or property, without due process of law; nor shall private property be taken for public use, without just compensation.\n<p>\n\nAmendment VI\n<p>\nIn all criminal prosecutions, the accused shall enjoy the right to a speedy and public trial, by an impartial jury of the State and district wherein the crime shall have been committed, which district shall have been previously ascertained by law, and to be informed of the nature and cause of the accusation; to be confronted with the witnesses against him; to have compulsory process for obtaining witnesses in his favor, and to have the Assistance of Counsel for his defence.\n<p>\nAmendment VII\n<p>\nIn Suits at common law, where the value in controversy shall exceed twenty dollars, the right of trial by jury shall be preserved, and no fact tried by a jury, shall be otherwise re-examined in any Court of the United States, than according to the rules of the common law.\n<p>\nAmendment VIII\n<p>\nExcessive bail shall not be required, nor excessive fines imposed, nor cruel and unusual punishments inflicted.\n<p>\nAmendment IX\n<p>\nThe enumeration in the Constitution, of certain rights, shall not be construed to deny or disparage others retained by the people.\n<p>\nAmendment X\n<p>\nThe powers not delegated to the United States by the Constitution, nor prohibited by it to the States, are reserved to the States respectively, or to the people.\"\n<p>\n", 5 "permalink" : "aRjNnLZkJkTyspAIoRGe", 6 "author" : "machine", 7 "title" : "Bill of Rights", 8 "tags" : [ 9 "watchmaker", 10 "santa", 11 "xylophone", 12 "math", 13 "handsaw", 14 "dream", 15 "undershirt", 16 "dolphin", 17 "tanker", 18 "action" 19 ], 20 "comments" : [ 21 { 22 "body" : "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", 23 "email" : "HvizfYVx@pKvLaagH.com", 24 "author" : "Santiago Dollins" 25 }, 26 { 27 "body" : "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", 28 "email" : "glbeRCMi@KwnNwhzl.com", 29 "author" : "Omar Bowdoin" 30 } 31 ], 32 "date" : ISODate("2012-11-20T05:05:15.231Z") 33 }
Esta coleção de postagens 500 foi gerada artificialmente, mas contém matrizes e quero mostrar como podemos manipular matrizes em um pipeline.
Vamos tentar encontrar as três tags mais populares e, para cada tag, também quero a lista de títulos de postagens que eles estão marcando.
Aqui está a minha solução em Java.
1 /** 2 * find the 3 most popular tags and their post titles 3 * @param posts sample_training.posts collection from the MongoDB Sample Dataset in MongoDB Atlas. 4 */ 5 private static void threeMostPopularTags(MongoCollection<Document> posts) { 6 Bson unwind = unwind("$tags"); 7 Bson group = group("$tags", sum("count", 1L), push("titles", "$title")); 8 Bson sort = sort(descending("count")); 9 Bson limit = limit(3); 10 Bson project = project(fields(excludeId(), computed("tag", "$_id"), include("count", "titles"))); 11 12 List<Document> results = posts.aggregate(List.of(unwind, group, sort, limit, project)).into(new ArrayList<>()); 13 System.out.println("==> 3 most popular tags and their posts titles"); 14 results.forEach(printDocuments()); 15 }
Ele me permite no estágio $group seguinte agrupar minhas marcações, contar as postagens e coletar os títulos em uma nova array
titles
.Aqui está o resultado final que obtive.
1 ==> 3 most popular tags and their posts titles 2 { 3 "count": 8, 4 "titles": [ 5 "Gettysburg Address", 6 "US Constitution", 7 "Bill of Rights", 8 "Gettysburg Address", 9 "Gettysburg Address", 10 "Declaration of Independence", 11 "Bill of Rights", 12 "Declaration of Independence" 13 ], 14 "tag": "toad" 15 } 16 { 17 "count": 8, 18 "titles": [ 19 "Bill of Rights", 20 "Gettysburg Address", 21 "Bill of Rights", 22 "Bill of Rights", 23 "Declaration of Independence", 24 "Declaration of Independence", 25 "Bill of Rights", 26 "US Constitution" 27 ], 28 "tag": "forest" 29 } 30 { 31 "count": 8, 32 "titles": [ 33 "Bill of Rights", 34 "Declaration of Independence", 35 "Declaration of Independence", 36 "Gettysburg Address", 37 "US Constitution", 38 "Bill of Rights", 39 "US Constitution", 40 "US Constitution" 41 ], 42 "tag": "hair" 43 }
Como você pode ver, alguns títulos são repetidos. Como eu disse anteriormente, a coleção foi gerada então os títulos das postagens não são únicos. Eu poderia resolver esse "problema" usando o operador $addToSet em vez do operador$push se isso fosse realmente um problema.
1 package com.mongodb.quickstart; 2 3 import com.mongodb.client.MongoClient; 4 import com.mongodb.client.MongoClients; 5 import com.mongodb.client.MongoCollection; 6 import com.mongodb.client.MongoDatabase; 7 import org.bson.Document; 8 import org.bson.conversions.Bson; 9 import org.bson.json.JsonWriterSettings; 10 11 import java.util.ArrayList; 12 import java.util.List; 13 import java.util.function.Consumer; 14 15 import static com.mongodb.client.model.Accumulators.push; 16 import static com.mongodb.client.model.Accumulators.sum; 17 import static com.mongodb.client.model.Aggregates.*; 18 import static com.mongodb.client.model.Filters.eq; 19 import static com.mongodb.client.model.Projections.*; 20 import static com.mongodb.client.model.Sorts.descending; 21 22 public class AggregationFramework { 23 24 public static void main(String[] args) { 25 String connectionString = System.getProperty("mongodb.uri"); 26 try (MongoClient mongoClient = MongoClients.create(connectionString)) { 27 MongoDatabase db = mongoClient.getDatabase("sample_training"); 28 MongoCollection<Document> zips = db.getCollection("zips"); 29 MongoCollection<Document> posts = db.getCollection("posts"); 30 threeMostPopulatedCitiesInTexas(zips); 31 threeMostPopularTags(posts); 32 } 33 } 34 35 /** 36 * find the 3 most densely populated cities in Texas. 37 * 38 * @param zips sample_training.zips collection from the MongoDB Sample Dataset in MongoDB Atlas. 39 */ 40 private static void threeMostPopulatedCitiesInTexas(MongoCollection<Document> zips) { 41 Bson match = match(eq("state", "TX")); 42 Bson group = group("$city", sum("totalPop", "$pop")); 43 Bson project = project(fields(excludeId(), include("totalPop"), computed("city", "$_id"))); 44 Bson sort = sort(descending("totalPop")); 45 Bson limit = limit(3); 46 47 List<Document> results = zips.aggregate(List.of(match, group, project, sort, limit)).into(new ArrayList<>()); 48 System.out.println("==> 3 most densely populated cities in Texas"); 49 results.forEach(printDocuments()); 50 } 51 52 /** 53 * find the 3 most popular tags and their post titles 54 * 55 * @param posts sample_training.posts collection from the MongoDB Sample Dataset in MongoDB Atlas. 56 */ 57 private static void threeMostPopularTags(MongoCollection<Document> posts) { 58 Bson unwind = unwind("$tags"); 59 Bson group = group("$tags", sum("count", 1L), push("titles", "$title")); 60 Bson sort = sort(descending("count")); 61 Bson limit = limit(3); 62 Bson project = project(fields(excludeId(), computed("tag", "$_id"), include("count", "titles"))); 63 64 List<Document> results = posts.aggregate(List.of(unwind, group, sort, limit, project)).into(new ArrayList<>()); 65 System.out.println("==> 3 most popular tags and their posts titles"); 66 results.forEach(printDocuments()); 67 } 68 69 private static Consumer<Document> printDocuments() { 70 return doc -> System.out.println(doc.toJson(JsonWriterSettings.builder().indent(true).build())); 71 } 72 }
O pipeline de agregação é muito poderoso. Acabamos de começar com esses dois exemplos, mas confie em mim se eu disser que é seu melhor aliado se você conseguir dominá-lo.
Se você quiser aprender mais e afundar seu conhecimento com mais rapidez, recomendamos conferir o treinamento Caminho do desenvolvedor MongoDB Java disponível gratuitamente na MongoDB University.