Passer au contenu principal

MongoDB

Introduction​

MongoDB est une base de données NoSQL orientée documents. Au lieu de tableaux et de lignes, les données sont stockées dans des documents au format similaire à JSON, ce qui permet une grande flexibilité dans le modèle de données.

Netuno offre la ressource _mongo pour l'intégration avec MongoDB, avec une abstraction low-code au-dessus du client officiel MongoDB pour Java, disponible pour les différents langages de programmation pris en charge par Netuno : JavaScript, Python, Ruby, Kotlin et Groovy.

Avant de continuer, il est recommandé de savoir comment créer des services dans Netuno, consultez le tutoriel REST - Services Web.

Un exemple d'implémentation des opérations présentées ici est disponible dans le service d'exemple mongo.js qui se trouve dans :

  • apps/demo/server/services/samples/javascript/mongo.js

Installation de MongoDB​

Avec Docker​

En supposant que Docker soit installé, il suffit de télécharger l'image MongoDB et de démarrer un nouveau conteneur :

docker pull mongo
docker run -d --name mongo -p 27017:27017 mongo

De cette façon, MongoDB est accessible à l'adresse localhost:27017 sans authentification.

Pour créer l'utilisateur de la base de données conformément à la configuration de l'application, exécutez :

docker exec -it mongo mongosh produits_db --eval 'db.createUser({ user: "produits_user", pwd: "12345678", roles: [{ role: "readWrite", db: "produits_db" }] })'

Avec Ubuntu​

MongoDB n'est pas inclus dans les dépôts officiels d'Ubuntu, il est donc nécessaire d'ajouter le dépôt officiel avant l'installation :

sudo apt-get install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] http://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org

Après l'installation, vous pouvez démarrer le service avec :

sudo systemctl start mongod

Avec Windows​

Exécutez l'installateur téléchargé et, à l'étape Service Configuration, activez Install MongoD as a Service pour que MongoDB démarre automatiquement. MongoDB est accessible à l'adresse localhost:27017 sans authentification.

Consultez la documentation officielle de MongoDB pour d'autres distributions et versions.

Configuration de l'application​

La configuration des connexions MongoDB se fait dans le fichier de configuration de l'application, selon l'environnement :

  • config/_development.json

Voir plus sur les fichiers de configuration de l'application.

Dans le fichier de configuration, ajoutez le bloc mongo, où chaque clé identifie une connexion :

{
...
"mongo": {
"magasin": "mongodb://magasin_user:12345678@localhost/magasin_db",
"produits": {
"username": "produits_user",
"password": "12345678",
"host": "127.0.0.1",
"port": "27017",
"database": "produits_db"
}
},
...
}

Une connexion peut être définie de deux manières :

  • Comme URL de connexion — la valeur est simplement une chaîne, par exemple "mongodb://magasin_user:12345678@localhost/magasin_db".
  • Comme objet de configuration — avec les champs :
    • url — s'il est défini, il est utilisé directement comme URL de connexion, en ignorant les autres champs.
    • protocol — le protocole de la connexion, par défaut mongodb.
    • username et password — les identifiants d'accès.
    • host — l'adresse du serveur, par défaut localhost.
    • port — le port de la connexion, par défaut 27017.
    • database — le nom de la base de données, par défaut le nom de la clé de connexion.
    • params — paramètres supplémentaires de la connexion ajoutés à l'URL, par exemple { "authSource": "admin" }. Ils ne sont appliqués que lorsque l'URL est construite à partir des champs de l'objet.

La connexion n'a pas besoin d'être la base de données principale de l'application. Contrairement à la configuration db, le bloc mongo est indépendant et permet plusieurs connexions simultanées.

Initialiser la connexion​

La première étape consiste à initialiser le client MongoDB avec la ressource _mongo.init(...), en passant le nom de la clé de configuration ou directement une URL de connexion :

const mongo = _mongo.init("produits")

Vous pouvez également initialiser la connexion directement avec une URL :

const mongo = _mongo.init("mongodb://localhost:27017/produits_db")

Lors de l'appel à _mongo.init() sans arguments, la clé de configuration default est utilisée. Il est recommandé de toujours passer le nom de la clé ou l'URL, afin que la connexion utilisée soit explicite.

Base de données et collections​

Une fois le client initialisé, obtenez l'instance de la base de données avec database("nom"). Ensuite, vous pouvez créer des collections et accéder à une collection existante :

const bd = mongo.database("produits_db")

bd.createCollection("produit")

const collection = bd.collection("produit")

Vous pouvez tester la connexion à la base de données avec ping(), et lister toutes les collections existantes avec collectionNames() :

bd.ping()

const noms = bd.collectionNames()

Vous pouvez renommer une collection avec renameCollection(), en passant le nom complet avec la base de données, ou le nom de la base de données et le nouveau nom de la collection séparément :

collection.renameCollection("produits_db.produit_archive")

collection.renameCollection("archives_db", "produit_archive")

Pour supprimer une collection et tous ses documents, utilisez drop() :

collection.drop()

Insérer des documents​

Les données sont représentées avec la ressource _val, via _val.map() pour les documents et _val.list() pour les listes de valeurs.

Insérer un seul document​

Avec insertOne(), insérez un document et obtenez l'ID généré :

const id = collection.insertOne(
_val.map()
.set("nom", "Ordinateur portable")
.set("quantite", 22)
.set("prix", 100)
.set("categorie", "ordinateurs")
)

Les documents peuvent contenir des structures imbriquées, comme des listes et d'autres documents :

collection.insertOne(
_val.map()
.set("nom", "Tablette")
.set("etiquettes",
_val.list()
.add("promo")
.add("nouveau")
)
.set("details",
_val.map()
.set("couleur", "bleu")
.set("stock", 10)
)
)

Insérer plusieurs documents​

Avec insertMany(), insérez plusieurs documents en une seule fois, en recevant la liste des IDs générés :

const ids = collection.insertMany(
_val.list()
.add(
_val.map()
.set("nom", "Smartphone")
.set("quantite", 18)
)
.add(
_val.map()
.set("nom", "Écran")
.set("quantite", 8)
)
)

Consulter des documents​

La méthode find() renvoie un MongoFindIterable, à partir duquel vous obtenez les documents avec all() (tous) ou first() (le premier, ou null s'il n'y en a aucun).

Pour obtenir tous les documents de la collection :

const documents = collection.find().all()

Pour interroger avec un filtre, utilisez la factory _mongo.filters() :

const documents = collection.find(
_mongo.filters().eq("nom", "Ordinateur portable")
).all()

Filtres​

La factory _mongo.filters() offre les principaux opérateurs de requête MongoDB :

OpérateurDescription
eq("champ", valeur)Égal à la valeur.
ne("champ", valeur)Différent de la valeur.
gt("champ", valeur)Supérieur à la valeur.
gte("champ", valeur)Supérieur ou égal à la valeur.
lt("champ", valeur)Inférieur à la valeur.
lte("champ", valeur)Inférieur ou égal à la valeur.
in("champ", valeur1, valeur2, ...)Égal à l'une des valeurs.
nin("champ", valeur1, valeur2, ...)Différent de toutes les valeurs.
and(filtre1, filtre2, ...)Correspond à tous les filtres.
or(filtre1, filtre2, ...)Correspond à au moins un des filtres.
not(filtre)Ne correspond pas au filtre.
regex("champ", "motif")Correspond à l'expression régulière.
exists("champ")Le champ existe dans le document.
text("recherche")Recherche textuelle.
type("champ", "type")Le champ est du type spécifié.
mod("champ", diviseur, reste)Le reste de la division du champ par le diviseur est égal à la valeur.
where("expression")Correspond aux documents qui satisfont une expression JavaScript, par exemple "this.prix > 100".
size("champ", n)Le champ tableau a exactement n éléments.

Par exemple, pour obtenir les produits avec un prix supérieur à 50 et une quantité inférieure à 30 :

const documents = collection.find(
_mongo.filters().and(
_mongo.filters().gt("prix", 50),
_mongo.filters().lt("quantite", 30)
)
).all()

Ou pour obtenir les produits dont le nom correspond à une expression régulière :

const documents = collection.find(
_mongo.filters().regex("nom", "^[LS]")
).all()

Parcourir les résultats​

Avec le résultat de la requête, vous pouvez parcourir tous les documents trouvés :

for (const document of collection.find().all()) {
_out.println(document.getString("nom"))
}

Vous pouvez également parcourir les résultats avec forEach() en passant une fonction :

collection.find().forEach((document) =>
_out.println(document.getString("nom"))
)

Tri​

Avec la factory _mongo.sorts(), vous définissez l'ordre des résultats, de manière ascendante ou descendante :

const documents = collection.find().sort(
_mongo.sorts().descending("prix")
).all()

Pour trier par plusieurs champs, utilisez orderBy() :

const documents = collection.find().sort(
_mongo.sorts().orderBy(
_mongo.sorts().descending("prix"),
_mongo.sorts().ascending("quantite")
)
).all()

Projection de champs​

Avec la factory _mongo.projections(), vous limitez les champs renvoyés dans les documents :

const documents = collection.find().projection(
_mongo.projections().include("nom", "quantite")
).all()

Pour combiner plusieurs projections :

const documents = collection.find().projection(
_mongo.projections().fields(
_mongo.projections().include("nom", "quantite"),
_mongo.projections().excludeId()
)
).all()

Les types de projection disponibles sont les suivants :

ProjectionDescription
include("champ", ...)Inclut uniquement les champs spécifiés.
exclude("champ", ...)Exclut les champs spécifiés.
excludeId()Exclut le champ _id.
fields(projection1, ...)Combine des projections.
slice("champ", n)Inclut uniquement les n premiers éléments du champ tableau.
slice("champ", saut, n)Inclut n éléments du champ tableau, à partir d'après les premiers saut éléments.
elemMatch("champ")Inclut uniquement le premier élément du champ tableau.
elemMatch("champ", filtre)Inclut uniquement le premier élément du tableau qui correspond au filtre.
computed("champ", expression)Ajoute un champ calculé basé sur une expression d'agrégation.

Limites et sauts​

Avec limit() et skip(), vous contrôlez la quantité de résultats renvoyés :

const documents = collection.find()
.skip(1)
.limit(2)
.all()

Mettre à jour des documents​

Avec la factory _mongo.updates(), vous définissez les modifications à appliquer, et avec updateOne() ou updateMany() vous appliquez les modifications aux documents qui correspondent au filtre :

collection.updateOne(
_mongo.filters().eq("nom", "Ordinateur portable"),
_mongo.updates().set("quantite", 42)
)

collection.updateMany(
_mongo.filters().eq("categorie", "ordinateurs"),
_mongo.updates().set("categorie", "en_vedette")
)

La factory _mongo.updates() offre les opérateurs de mise à jour :

OpérateurDescription
set("champ", valeur)Définit la valeur d'un champ.
unset("champ")Supprime un champ du document.
rename("champ", "nouveauNom")Renomme un champ.
push("champ", valeur)Ajoute une valeur à un tableau.
combine(miseAJour1, miseAJour2, ...)Combine plusieurs mises à jour en une seule.

Pour appliquer plusieurs modifications en une seule fois, utilisez combine() :

const miseAJour = _mongo.updates().combine(
_mongo.updates().set("quantite", 42),
_mongo.updates().rename("autre", "plus")
)

collection.updateOne(
_mongo.filters().eq("nom", "Ordinateur portable"),
miseAJour
)

Rechercher et mettre à jour​

Avec findOneAndUpdate(), recherchez et mettez à jour un document de manière atomique, en renvoyant le document original (ou null s'il n'y en a aucun) :

const ancien = collection.findOneAndUpdate(
_mongo.filters().eq("nom", "Ordinateur portable"),
_mongo.updates().set("quantite", 42)
)

Remplacer des documents​

Avec replaceOne(), remplacez le document entier (contrairement aux mises à jour, il ne peut pas contenir d'opérateurs de mise à jour) :

collection.replaceOne(
_mongo.filters().eq("nom", "Ordinateur portable"),
_val.map()
.set("nom", "Ordinateur portable")
.set("quantite", 50)
.set("prix", 150)
.set("categorie", "en_vedette")
)

Pour rechercher et remplacer de manière atomique, en renvoyant l'ancien document, utilisez findOneAndReplace() :

const ancien = collection.findOneAndReplace(
_mongo.filters().eq("nom", "Ordinateur portable"),
_val.map()
.set("nom", "Ordinateur portable")
.set("quantite", 50)
)

Supprimer des documents​

Avec deleteOne() et deleteMany(), supprimez les documents qui correspondent au filtre :

collection.deleteOne(
_mongo.filters().eq("nom", "Ordinateur portable")
)

collection.deleteMany(
_mongo.filters().eq("categorie", "inactif")
)

Pour rechercher et supprimer un document de manière atomique, en renvoyant le document supprimé, utilisez findOneAndDelete() :

const ancien = collection.findOneAndDelete(
_mongo.filters().eq("nom", "Tablette")
)

Pour supprimer tous les documents de la collection, passez un filtre vide à l'aide de _mongo.valToDoc() :

collection.deleteMany(_mongo.valToDoc(_val.map()))

Compter des documents​

Avec countDocuments(), obtenez le nombre de documents de la collection, éventuellement avec un filtre. estimatedDocumentCount() est plus rapide car il utilise les métadonnées de la collection, mais sans filtre :

const total = collection.countDocuments()

const totalPrincipal = collection.countDocuments(
_mongo.filters().eq("categorie", "ordinateurs")
)

const estime = collection.estimatedDocumentCount()

Index​

Avec la factory _mongo.indexes(), créez des index pour améliorer les performances des requêtes. Pour créer un index, il suffit de passer les spécifications à createIndex() :

collection.createIndex(
_mongo.indexes().ascending("quantite")
)

collection.createIndex(
_mongo.indexes().compoundIndex(
_mongo.indexes().descending("prix"),
_mongo.indexes().ascending("quantite")
)
)

Vous pouvez indiquer à MongoDB quel index utiliser dans une requête, avec hint() :

const documents = collection.find().hint(
_mongo.valToDoc(
_val.map().set("quantite", 1)
)
).all()

Avec min() et max(), vous limitez la plage de valeurs de l'index parcourues par la requête :

const documents = collection.find()
.min(
_mongo.valToDoc(
_val.map().set("quantite", 10)
)
)
.max(
_mongo.valToDoc(
_val.map().set("quantite", 50)
)
)
.all()

Agrégations​

Pour des analyses plus avancées, aggregate() permet d'exécuter un pipeline d'agrégation, où chaque étape est définie avec la factory _mongo.aggregates() et les accumulateurs avec _mongo.accumulators().

Par exemple, pour regrouper les produits par catégorie et additionner le prix de chaque groupe :

const documents = collection.aggregate(
_mongo.aggregates().match(
_mongo.filters().eq("categorie", "ordinateurs")
),
_mongo.aggregates().group(
"$categorie",
_mongo.accumulators().sum("total", "$prix")
),
_mongo.aggregates().sort(
_mongo.sorts().descending("total")
)
).all()

Les étapes d'agrégation disponibles sont les suivantes :

ÉtapeDescription
match(filtre)Sélectionne uniquement les documents qui correspondent au filtre.
group("$champ", accumulateurs)Regroupe les documents par le champ et applique les accumulateurs.
project(projection)Ajoute, supprime ou modifie des champs dans les documents.
sort(tri)Trie les documents.
limit(n)Limite aux n premiers documents.
skip(n)Saute les n premiers documents.
count()Compte le nombre de documents.
lookup("collection", "champLocal", "champEtranger", "comme")Effectue une jointure avec une autre collection.

Et les accumulateurs les plus utilisés :

AccumulateurDescription
sum("champ", "$expression")Somme des valeurs.
avg("champ", "$expression")Moyenne des valeurs.
min("champ", "$expression")Valeur minimale.
max("champ", "$expression")Valeur maximale.
first("champ", "$expression")Valeur du premier document.
last("champ", "$expression")Valeur du dernier document.
top("champ", tri, "$expression")Valeur du haut selon le tri.
bottom("champ", tri, "$expression")Valeur du bas selon le tri.

Dans les accumulateurs, l'expression préfixée par $ fait référence au champ d'un document d'entrée, par exemple "$prix".

Conversion entre Values et BSON​

Il est parfois nécessaire de convertir entre les objets _val de Netuno et les documents BSON de MongoDB :

const document = _mongo.valToDoc(
_val.map().set("nom", "Ordinateur portable")
)

const valeurs = _mongo.docToVal(document)

Opérations avec options​

Les factories *Options() de la ressource _mongo renvoient les objets d'options par défaut du driver MongoDB, et vous pouvez enchaîner leurs méthodes directement. Par exemple, upsert(true) insère le document lorsque le filtre ne correspond à aucun document existant :

const ancien = collection.findOneAndUpdate(
_mongo.filters().eq("nom", "Écran"),
_mongo.updates().set("quantite", 8),
_mongo.findOneAndUpdateOptions().upsert(true)
)

Lorsque le document est inséré, findOneAndUpdate() renvoie null.

Les factories d'options disponibles sont : insertOneOptions(), insertManyOptions(), updateOptions(), findOneAndUpdateOptions(), replaceOptions(), findOneAndReplaceOptions(), deleteOptions(), findOneAndDeleteOptions(), countOptions(), estimatedDocumentCountOptions(), dropCollectionOptions() et textSearchOptions().

Fermer la connexion​

En terminant, il est de bonne pratique de fermer la connexion à MongoDB :

_mongo.close()

Conclusion​

Avec la ressource _mongo, Netuno offre une abstraction low-code et polyglotte au-dessus du client officiel MongoDB pour Java, permettant de travailler avec des documents, filtres, tris, projections, mises à jour, index et agrégations en JavaScript, Python, Ruby, Kotlin et Groovy.

Pour plus de détails sur toutes les méthodes disponibles, consultez la documentation de la ressource _mongo et de la classe MongoCollection.

Bon développement !