Tutorial MongoDB pour les utilisateurs de SQL Server

Les bases de données relationnelles font partie depuis plus de 30 ans du quotidien de nombreux développeurs. Qu'elles soient commerciales (Microsoft SQL Server, Oracle...) ou Open Source (PostgreSQL, MySQL...), elles se basent toutes sur un modèle de stockage dont le concept remonte aux années 70.

Avec l'intérêt grandissant de la communauté pour Node.js, j'ai eu envie de découvrir une autre approche de stockage et de modélisation des données. Un des leaders dans le domaines des bases de données NoSQL est MongoDB.

MongoDB (de l'anglais humongous qui veut dire énorme) est un système de gestion de base de données orienté documents, répartissable sur plusieurs serveurs, efficace pour les requêtes simples et ne nécessitant pas de schéma prédéfini des données.





MongoDB permet de manipuler des objets structurés au format BSON (JSON binaire), sans schéma prédéterminé. En d'autres termes, des champs peuvent être ajoutés à tout moment, sans reconfiguration de la base.

Les données prennent la forme de documents enregistrés eux-mêmes dans des collections, une collection contenant un nombre quelconque de documents. Les collections sont comparables aux tables, et les documents aux enregistrements des bases de données relationnelles.

Contrairement aux bases de données relationnelles, les champs d'un enregistrement sont libres et peuvent être différents d'un enregistrement à un autre au sein d'une même collection. Le seul champ commun et obligatoire est le champ de clé principale ("id").

A qui s'adresse cet article?

Ce tutorial s'adresse principalement aux utilisateurs de SQL Server sous Windows, dont je fais partie. Mon but est de les guider rapidement dans la découverte de MongoDB, en insistant sur les différences fondamentales.

Un prochain tutorial sera consacré à l'utilisation de MongoDB avec C#.

Installation

Rendez-vous sur le site officiel de MongoBD: http://www.mongodb.org/downloads

MongoDB ne nécessite aucune procédure d'installation particulière. Dés lors, vous pouvez télécharger la version ZIP pour votre version de Windows.

Par exemple, pour Windows 7 Pro 64bits, téléchargez le fichier suivant:
https://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-2.6.3.zip

Une fois le fichier ZIP décompressé, copiez tout le contenu du répertoire bin vers le répertoire c:\mongo

Créer un répertoire c:\data\db, qui sera utilisé par défaut pour le stockage des données.

Démarrer le serveur

Ouvrez une console (Start-R, cmd) et tapez les commandes suivantes:

c:
cd \mongo
mongod

ou

mongod --dbpath <votre chemin de stockage des données>

Le serveur démarre et indique qu'il attend des connexions sur le port 27017:



Installer MongoDB comme service Windows (optionnel)

MongoDB peut évidemment s'installer sous la forme d'un service Windows. Cette forme d'installation n'est pas obligatoire si vous utilisez uniquement MongoDB en développement. Pour commencer, créez un répertoire de log qui sera utilisé pour tracer les activités du service:

md "C:\mongo\log"

Créez ensuite un fichier de configuration indiquant où se trouve le répertoire de log:

echo logpath="C:\mongo\log" > "C:\mongo\mongod.cfg"

Enfin, tapez la commande suivante pour installer le service:

"C:\mongo\mongod.exe" --config "C:\mongo\mongod.cfg" --install

Pour stopper le service, tapez:

net stop MongoDB

Enfin, si vous souhaitez désinstaller le service:

"c:\mongo\mongod.exe" --remove

Se connecter à MongoDB

MongoDB propose un logiciel client en mode texte. Ouvrez une nouvelle console, déplacez-vous dans le dossier c:\mongo et tapez tout simplement:

mongo

Un message d'invite vous montre la version de MongoDB et indique que vous vous trouvez par défaut dans la base de données test.

MongoDB shell version: 2.6.3
connecting to: test
>



Créer une nouvelle base de données

Dans MongoDB, la commande use permet non seulement de se déplacer dans une base de données existante, mais aussi de la créer, si celle-ci n'existe pas encore. Tapez:

use tutorial

switched to db tutorial

Insérer un enregistrement (pardon, un document)

Comme nous l'avons vu en introduction, MongoDB utilise le format BJSON pour le stockage interne des documents. JSON est donc tout naturellement utilisé pour l'encodage et la lecture des données. Pour en savoir plus sur ce format, rendez-vous sur http://msdn.microsoft.com/en-us/library/bb299886.aspx

Nous allons commencer par créer un nouveau document dans la collection customers (clients). Inutile de créer cette collection; le premier document inséré provoquera automatiquement sa création!

Tapez la commande suivante pour insérer votre premier document:

db.customers.insert({
    firstname: 'John',
    lastname: 'Doe',
    birthdate: '21/04/1978',
    occupation: 'Software Engineer',
    country: 'USA'
});

MongoDB affiche le message suivant, confirmant qu'un document a bien été inséré:

WriteResult({ "nInserted" : 1 })
>

Insérons un second document:

db.customers.insert({
    firstname: 'Mark',
    lastname: 'Smith',
    birthdate: '31/12/1984',
    occupation: 'DBA',
    country: 'USA'
});

Lister le contenu de la table (pardon, de la collection)

Nous avons à présent deux documents dans la collection customers. Pour les lister, utilisez la méthode find():

db.customers.find()

MongoDB nous renvoie le contenu des deux documents en format JSON. Vous pouvez immédiatement reconnaître le champ clef ID qui a été créé automatiquement et qui contient un identifant global unique (GUID):


{ "_id" : ObjectId("53d1fbe372cdde64bb1768a6"), "firstname" : "John", "lastname"
 : "Doe", "birthdate" : "21/04/1978", "occupation" : "Software Engineer", "count
ry" : "USA" }
{ "_id" : ObjectId("53d1fc1972cdde64bb1768a7"), "firstname" : "Mark", "lastname"
 : "Smith", "birthdate" : "31/12/1984", "occupation" : "DBA", "country" : "USA"
}
>

Ajouter un document hiéarchisé

Nous avons pour l'instant deux clients dans la collection customers. Nous aimerions pouvoir maintenant stocker plusieurs numéros de téléphones par client. Dans une base de données relationnelle, nous devrions créer une table fille phonenumbers liée à la table customers par une relation de type 0..N.

Cette notion n'existe plus dans MongoDB, car les propriétés d'un document peuvent être hiérarchisées. En notation JSON, notre nouveau document a insérer ressemblera à ceci: 

db.customers.insert({
    firstname: 'Fabrice',
    lastname: 'Kauffmann',
    birthdate: '15/01/1972',
    gender: 'm',
    occupation: 'Software Architect',
    country: 'Belgium',
phonenumbers: [
{location: "office", number: "+352 12 34 56 789" },
{location: "private", number: "+32 63 12 34 56" }
]
});

Affichons maintenant le contenu de la collection customers:

db.customers.find()

Comme on peut le voir, les documents d'une collection ne doivent pas avoir nécessairement la même structure:

{ "_id" : ObjectId("53d1fbe372cdde64bb1768a6"), "firstname" : "John", "lastname"
 : "Doe", "birthdate" : "21/04/1978", "occupation" : "Software Engineer", "count
ry" : "USA" }
{ "_id" : ObjectId("53d1fc1972cdde64bb1768a7"), "firstname" : "Mark", "lastname"
 : "Smith", "birthdate" : "31/12/1984", "occupation" : "DBA", "country" : "USA"
}
{ "_id" : ObjectId("53d1ff1072cdde64bb1768a9"), "firstname" : "Fabrice", "lastna
me" : "Kauffmann", "birthdate" : "15/01/1972", "gender" : "m", "occupation" : "S
oftware Architect", "country" : "Belgium", "phonenumbers" : [ { "location" : "of
fice", "number" : "+352 12 34 56 789" }, { "location" : "private", "number" : "+
32 63 12 34 56" } ] }
>

La méthode pretty() permet de formater le résultat JSON pour une meilleure lecture:

db.customers.find().pretty()

Qui nous donne en sortie:

{
        "_id" : ObjectId("53d1ffbf72cdde64bb1768aa"),
        "firstname" : "John",
        "lastname" : "Doe",
        "birthdate" : "21/04/1978",
        "occupation" : "Software Engineer",
        "country" : "USA"
}
{
        "_id" : ObjectId("53d1ffc972cdde64bb1768ab"),
        "firstname" : "Mark",
        "lastname" : "Smith",
        "birthdate" : "31/12/1984",
        "occupation" : "DBA",
        "country" : "USA"
}
{
        "_id" : ObjectId("53d1ffce72cdde64bb1768ac"),
        "firstname" : "Fabrice",
        "lastname" : "Kauffmann",
        "birthdate" : "15/01/1972",
        "gender" : "m",
        "occupation" : "Software Architect",
        "country" : "Belgium",
        "phonenumbers" : [
                {
                        "location" : "office",
                        "number" : "+352 12 34 56 789"
                },
                {
                        "location" : "private",
                        "number" : "+32 63 12 34 56"
                }
        ]
}

Modifier un enregistrement

Encore une différence fondamentable avec le langage SQL: la méthode update(), par défaut, ne met à jour que le premier document qui répond aux critères de sélection passés dans le premier argument.

Ici, nous allons changer le pays du client Fabrice Kauffmann:

db.customers.update({firstname: "Fabrice", lastname: "Kauffmann"}, {$set: {country: "Luxembourg"}})

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

Nous allons ensuite contrôler que la modification a bien été exécutée:

db.customers.find({lastname: "Kauffmann"}).pretty()

{
        "_id" : ObjectId("53d1ffce72cdde64bb1768ac"),
        "firstname" : "Fabrice",
        "lastname" : "Kauffmann",
        "birthdate" : "15/01/1972",
        "gender" : "m",
        "occupation" : "Software Architect",
        "country" : "Luxembourg",
        "phonenumbers" : [
                {
                        "location" : "office",
                        "number" : "+352 12 34 56 789"
                },
                {
                        "location" : "private",
                        "number" : "+32 63 12 34 56"
                }
        ]
}

Ajouter une propriété à un document

Autre particularité très intéressante: mettre à jour un champ qui n'existe pas encore crée ce champ! Dans la ligne suivante, nous modifions le client Fabrice Kauffmann pour lui ajouter une propriété company:

db.customers.update({firstname: "Fabrice", lastname: "Kauffmann"}, {$set: {company: "AOA Simulations"}})

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

Examinons le document mis-à-jour:

db.customers.find({lastname: "Kauffmann"}).pretty()

{
        "_id" : ObjectId("53d1ffce72cdde64bb1768ac"),
        "firstname" : "Fabrice",
        "lastname" : "Kauffmann",
        "birthdate" : "15/01/1972",
        "gender" : "m",
        "occupation" : "Software Architect",
        "country" : "Luxembourg",
        "phonenumbers" : [
                {
                        "location" : "office",
                        "number" : "+352 12 34 56 789"
                },
                {
                        "location" : "private",
                        "number" : "+32 63 12 34 56"
                }
        ],
        "company" : "AOA Simulations"
}

Chercher et trier des documents

Une condition de type AND se programme en passant en argument une suite de clefs / valeurs. Dans l'exemple ci-dessous, nous recherchons tout les clients dont la fonction est DBA aux USA:

db.customers.find({country: "USA", occupation: "DBA"}).pretty()

{
        "_id" : ObjectId("53d1ffc972cdde64bb1768ab"),
        "firstname" : "Mark",
        "lastname" : "Smith",
        "birthdate" : "31/12/1984",
        "occupation" : "DBA",
        "country" : "USA"
}

Tous les clients dont le pays est Luxembourg OU USA:

db.customers.find({$or: [{country: "Luxembourg"}, {country: "USA"}]})

Tous les clients dont le genre est masculin ET le pays est Luxembourg OU USA:

db.customers.find({gender: "m", $or: [{country: "Luxembourg"}, {country: "USA"}]})

Tous les clients dont le salaire est inférieur à 20.000:

db.customers.find(salary: {$lt: 20000})

Enfin, la méthode sort() permet de spécifier un ou plusieurs critères de tri (-1 est descendant):

db.customers.find({country: "USA"}).sort({lastname: 1})

Définir la liste des propriétés à retourner

La métode find() permet de préciser en second paramètre la liste des propriétés à retourner:

db.customers.find({salary: {$lt: 20000}}, {country: 1})

Ce qui nous donne en sortie:

{ "_id" : ObjectId("53d1ffbf72cdde64bb1768aa"), "country" : "USA" }
{ "_id" : ObjectId("53d1ffc972cdde64bb1768ab"), "country" : "USA" }

Mettre à jour plusieurs documents

Comme nous l'avons vu précédemment, la méthode update() ne modifie que le premier document qui répond au critère de recherche passé en argument. Pour mettre à jour tous les éléments d'une collection, utilisez l'option multi:

db.customers.update({}, {$set: {salary: 10000}}, {multi: true})

WriteResult({ "nMatched" : 3, "nUpserted" : 0, "nModified" : 2 })

Attention, l'option upsert: true permet de créer un enregistrement si la méthode update() ne trouve aucun document.

Détruire une base de données

Après tous ces tests, il est peut-être temps de supprimer notre base de données:

use tutorial
db.dropDatabase()