MongoDB یک پایگاه داده مبتنی بر داکیومنت و از نوع NoSQL است. در این پایگاه داده، تمام اطلاعات بهصورت داکیومنتهای JSON ذخیره میشوند که بر اساس نوع داده، در مجموعههایی به نام collection سازماندهی شدهاند. MongoDB به دلیل سادگی در استفاده و قدرت بالا، به یکی از محبوبترین گزینهها در میان پایگاههای داده NoSQL تبدیل شده است. در این مقاله، با هدف ارائه یک آموزش MongoDB جامع، به بررسی مفاهیم و قابلیتهای کلیدی آن میپردازیم؛ مفاهیمی که تمام جنبههای اساسی این پایگاه داده را پوشش میدهند.
برای شروع آموزش MongoDB، ابتدا باید این پایگاه داده را روی سیستم خود نصب کنیم. همچنین برای تعامل با MongoDB نیاز به یک ابزار command line داریم؛ اینجا ابزار Mongosh وارد عمل میشود. هنگام نصب، باید به این نکته توجه داشته باشیم که باید نسخه Community Edition را دریافت کنیم، نه نسخه Enterprise. در ادامه لینکهای رسمی دانلود این دو ابزار ارائه شدهاند:
پس از نصب، کافی است یک ترمینال باز کرده و دستور mongosh را اجرا کنیم تا وارد محیط کار با MongoDB شویم.
برای درک بهتر MongoDB، ابتدا باید با تعدادی از واژگان پایهای مربوط به پایگاه دادههای آن آشنا شویم:
اولین اصطلاح، «Database» است. یک پایگاه داده، ظرفی برای نگهداری collectionها است. در MongoDB، پایگاه داده از نظر مفهومی مشابه همان پایگاه داده در SQL است. معمولاً هر پروژه یک پایگاه داده دارد که شامل مجموعههای مختلفی از دادههاست.
Collection، گروهی از داکیومنتها درون یک پایگاه داده است. این مفهوم با جدولها در پایگاه دادههای SQL قابل مقایسه است. معمولاً برای هر مدل داده، یک collection جداگانه تعریف میشود. به عنوان مثال، اپلیکیشن ما ممکن است collectionهایی به نام users، posts و products داشته باشد.
Document، یک رکورد درون یک collection است. این مورد از نظر مفهومی معادل سطر در یک جدول SQL است. هر document معمولاً نمایانگر یک آبجکت مشخص در collectionاش است. در MongoDB، داکیومنتها در اصل همان آبجکتهای JSON هستند.
آخرین اصطلاحی که باید بشناسیم، Field است. فیلد، یک جفت key-value درون یک document است که از نظر مفهومی معادل ستون در SQL محسوب میشود. هر document میتواند شامل فیلدهایی مانند name، address یا hobbies باشد. یکی از تفاوتهای مهم میان MongoDB و پایگاه دادههای رابطهای مانند SQL این است که در MongoDB، فیلدها میتوانند مقادیر پیچیدهتری مانند آرایهها یا آبجکتهای JSON داشته باشند، نه فقط رشته، عدد یا مقادیر بولین. همچنین برخلاف SQL که تمام ردیفها باید ساختار یکسانی داشته باشند، در MongoDB میتوان داکیومنتهایی با ساختار متفاوت را در یک collection ذخیره کرد. برای مثال، در collection users، ممکن است یک document شامل فیلدهای name و age باشد و document دیگر فیلدهای name، address و hobbies را داشته باشد.
پیش از شروع کار با دادهها، ابتدا باید با چند دستور اولیه آشنا شویم که امکان تعامل با پایگاه دادهها را فراهم میکنند.
mongoshاولین دستور، mongosh است. این دستور در ترمینال اجرا میشود و دسترسی مستقیم به نصب لوکال MongoDB را فراهم میکند. تمامی دستورات بعدی مقاله، در همین محیط mongosh اجرا خواهند شد.
show dbsدستور show dbs لیستی از تمام پایگاه دادههای MongoDB را نمایش میدهد. اگر این دستور را اجرا کنیم، احتمالاً مشاهده خواهیم کرد که برخی پایگاه دادهها از پیش و همزمان با نصب MongoDB ایجاد شدهاند.
use <dbname>این دستور به ما اجازه میدهد تا به یک پایگاه داده خاص با نام مشخص دسترسی پیدا کرده یا آن را ایجاد کنیم. برای مثال، اجرای دستور use mydb ما را به پایگاه دادهای با نام mydb منتقل میکند. حتی اگر این پایگاه داده از قبل وجود نداشته باشد، MongoDB همچنان ما را به آن منتقل کرده و در صورت درج داده، آن را بهصورت خودکار ایجاد خواهد کرد. برخلاف SQL، در MongoDB نیازی به دستور جداگانه برای ساخت پایگاه داده یا collection وجود ندارد؛ این ساختارها هنگام وارد کردن دادهها بهطور خودکار ساخته میشوند.
dbدستور db نام پایگاه دادهای که در حال حاضر در آن قرار داریم را چاپ میکند.
clsاین دستور صفحه ترمینال را پاک میکند و باعث میشود محیط ما تمیزتر شود.
show collectionsاگر به یک پایگاه داده متصل هستیم، با اجرای دستور show collections میتوانیم فهرستی از تمام collectionهای موجود در آن پایگاه داده را مشاهده کنیم.
db.dropDatabase()این دستور پایگاه داده جاری و تمام دادههای موجود در آن را حذف میکند. همانطور که مشاهده میکنیم، ساختار این دستور شباهت زیادی به کدهای جاوااسکریپت دارد. در واقع، بسیاری از دستورات MongoDB به همین شکل طراحی شدهاند که برای توسعهدهندگانی که با جاوااسکریپت آشنا هستند، درک MongoDB را بسیار سادهتر میکند.
exitآخرین دستور پایه، exit است که برای خروج از session mongosh که با اجرای command mongosh آغاز شده، استفاده میشود.
در این بخش، با دستورات Create (ایجاد)، Read (خواندن)، Update (بهروزرسانی) و Delete (حذف) در MongoDB آشنا میشویم. تمامی این دستورات بر روی collectionای مشخص در یک پایگاه داده مشخص اجرا میشوند. برای مثال، اگر بخواهیم تمام رکوردهای موجود در collection users را در پایگاهداده جاری مشاهده کنیم، از دستور db.users.find() استفاده میکنیم.
فرآیند ایجاد documentها در MongoDB بسیار ساده است؛ چرا که تنها دو روش اصلی برای این کار وجود دارد که هر دو ساختاری مشابه دارند:
insertOneاین تابع، یک آبجکت را بهعنوان ورودی دریافت کرده و آن را بهصورت یک document در collection مشخص درج میکند. نکته جالب اینجاست که نیازی به تعیین ID برای documentها نیست؛ زیرا MongoDB بهطور خودکار یک فیلد به نام _id با مقدار یکتا به هر document اضافه میکند.
// Insert a user with the name Kyle
db.users.insertOne({ name: "Kyle" })
insertManyاین تابع دقیقاً مانند insertOne عمل میکند، با این تفاوت که بهجای یک آبجکت، آرایهای از آبجکتها را دریافت کرده و آنها را بهصورت گروهی درج میکند.
// Insert a user with the age of 26 and a second user with the name Kyle
db.users.insertMany([{ age: 26 }, { name: "Kyle" }])
خواندن دادهها در MongoDB پیچیدهتر از سایر عملیاتهاست و همین بخش است که معمولاً باعث سردرگمی کاربران میشود.
findبرای دریافت تمام documentهای موجود در یک collection، میتوانیم از متد find بدون هیچ پارامتر اضافی استفاده کنیم.
// Get all users db.users.find()
find(<filterObject>)اغلب موارد، هدف ما دریافت تمام documentهای collection نیست، بلکه میخواهیم فقط document خاصی را بازیابی کنیم. با ارسال یک آبجکت فیلتر به متد find، فقط documentهایی که با آن فیلتر تطابق دارند، return میشوند. بهصورت پیشفرض، MongoDB از مقایسه برابری استفاده میکند و اگر چندین فیلد را در یک آبجکت فیلتر وارد کنیم، فقط documentهایی که تمام آن شرایط را داشته باشند، return میشوند.
// Get all users with the name Kyle
db.users.find({ name: "Kyle" })
// Get all users whose address field has a zip field with the value 12345
db.users.find({ "address.zip": "12345" })
find(<filterObject>, <selectObject>)در این حالت، میتوانیم با ارسال آبجکت دومی به تابع find مشخص کنیم که کدام فیلدها در نتیجه return شوند. در این آبجکت، هر کلید نمایانگر نام یک فیلد است و مقدار آن یا ۱ (برای نمایش) یا ۰ (برای عدم نمایش) خواهد بود. بهصورت پیشفرض، فیلد _id همیشه در نتایج وجود دارد، مگر اینکه صراحتاً آن را از نتایج حذف کنیم.
// Get all users with the name Kyle and return the name, age, and _id fields
db.users.find({ name: "Kyle" }, { name: 1, age: 1 })
// Get all users and return all fields except the age field
db.users.find({}, { age: 0 })
findOneاین متد کاملاً مشابه find است، با این تفاوت که فقط اولین documentای که با شرط فیلتر مطابقت داشته باشد را برمیگرداند.
// Get the first user with the name Kyle
db.users.findOne({ name: "Kyle" })
countDocumentsاین متد، تعداد تمام documentهایی را که با شرط فیلتر داده شده تطابق دارند، return میکند.
// Get the count of users with the name Kyle
db.users.countDocuments({ name: "Kyle" })
فرآیند بهروزرسانی documentها در MongoDB نسبت به SQL کمی پیچیدهتر است، چرا که راهکارهای متعددی برای انجام این کار وجود دارد.
updateOneمتداولترین روش برای بهروزرسانی documentها، استفاده از تابع updateOne است. این تابع اولین documentای را که با فیلتر مشخص شده مطابقت داشته باشد، پیدا کرده و سپس بر اساس اطلاعات موجود در پارامتر دوم، آن را بهروزرسانی میکند. پارامتر دوم میتواند شامل گزینههای متعددی برای اعمال تغییرات باشد که در بخشهای بعدی مقاله به آنها خواهیم پرداخت.
// Update the age of the first user with an age of 20 to 21
db.users.updateOne({ age: 20 }, { $set: { age: 21 } })
updateManyاین متد دقیقاً مشابه updateOne عمل میکند، با این تفاوت که تمام documentهایی را که با شرط فیلتر تطابق دارند، بهروزرسانی میکند؛ نه فقط اولین مورد را.
// Update the age of all users with the age of 14 by adding 2 to their age
db.users.updateMany({ age: 14 }, { $incr: { age: 2 } })
replaceOneاین متد هم عملکردی مشابه updateOne دارد، با این تفاوت که بهجای بهروزرسانی برخی فیلدها، کل document را با document جدید جایگزین میکند. در اغلب مواقع، استفاده از این روش توصیه نمیشود؛ چرا که تمامی فیلدهایی که در آبجکت جدید تعریف نشدهاند (به جز فیلد _id) حذف خواهند شد.
// Replace the first user with an age of 14 with an object that only has a name field
db.users.replaceOne({ age: 14 }, { name: "Kyle" })
در حالی که عملیات Read و Update نسبتاً پیچیده بودند، خوشبختانه حذف document در MongoDB بسیار ساده است.
deleteOneاین متد، اولین documentای را که با شرط فیلتر مطابقت داشته باشد، حذف میکند.
// Delete the first user with the age of 20
db.users.deleteOne({ age: 20 })
deleteManyاین متد نیز مشابه deleteOne عمل میکند، اما بهجای حذف یک document، تمام documentهایی که با شرط فیلتر همخوانی داشته باشند را حذف میکند.
// Delete all users with the age of 14
db.users.deleteMany({ age: 14 })
متدهای CRUD که تا اینجا بررسی کردیم، نیازهای پایهای ما را در آموزش MongoDB برآورده میکنند. اما راهکارهایی نیز وجود دارد که با استفاده از آنها میتوان این متدها را قدرتمندتر و انعطافپذیرتر کرد.
اولین روش برای بهبود عملکرد متدهای بالا، گسترش قابلیتهای آبجکت فیلتر است. تمام متدهای Read، Update و Delete که پیشتر در این آموزش MongoDB معرفی شدند، بهعنوان پارامتر اول، یک آبجکت فیلتر دریافت میکنند. این آبجکت مشخص میکند که کدام documentها باید بازیابی، بهروزرسانی یا حذف شوند. تاکنون فقط از فیلترهای مبتنی بر تطابق دقیق استفاده کردهایم، اما MongoDB این امکان را میدهد که بهجای یک مقدار ساده، یک آبجکت بهعنوان مقدار فیلد ارسال کنیم تا شرطهای پیچیدهتری تعریف کنیم. همچنین میتوان چندین فیلتر را با یکدیگر ترکیب کرده یا بهصورت تودرتو ساختاربندی کرد.
$eqسادهترین فیلتر پیچیده، $eq است که برای بررسی برابری استفاده میشود. این فیلتر دقیقاً مانند فیلترهای سادهای که پیشتر بررسی کردیم عمل میکند و کاربرد آن زمانی است که بخواهیم مقدار یک فیلد، برابر با مقدار مشخصی باشد.
// This is essentially the same as db.users.find({ name: "Kyle" })
db.users.find({ name: { $eq: "Kyle" } })
$neqفیلتر $neq معادل منطقی معکوس $neq است و برای بررسی نابرابری به کار میرود. یعنی documentهایی را return میکند که مقدار فیلد مورد نظر با مقدار مشخص شده متفاوت باشد.
// Get all users with a name other than Kyle
db.users.find({ name: { $neq: "Kyle" } })
$gt و یا $gteاین فیلترها برای مقایسههای «بزرگتر از» و «بزرگتر یا مساوی» استفاده میشوند.
$gt: بررسی مقدار بزرگتر از$gte: بررسی مقدار بزرگتر یا مساوی// Get all users with an age greater than 18
db.users.find({ age: { $gt: 18 } })
// Get all users with an age greater than or equal to 21
db.users.find({ age: { $gte: 21 } })
$lt و یا $lteاین فیلترها عملکردی مشابه $gt و $gte دارند، با این تفاوت که برای مقایسههای «کوچکتر از» و «کوچکتر یا مساوی» بهکار میروند.
$lt: بررسی مقدار کوچکتر از$lte: بررسی مقدار کوچکتر یا مساوی// Get all users with an age less than 18
db.users.find({ age: { $lt: 18 } })
// Get all users with an age less than or equal to 21
db.users.find({ age: { $lte: 21 } })
$inاین فیلتر تمامی documentهایی را که مقدار یکی از فیلدهایشان با یکی از مقادیر موجود در آرایه مطابقت داشته باشد، برمیگرداند. این روش زمانی کاربرد دارد که بخواهیم چند مقدار مشخص را به عنوان گزینه معتبر تعریف کنیم.
// Get all users with the name Kyle or John
db.users.find({ name: { $in: ["Kyle", "John"] } })
$ninبرعکس $in، فیلتر $nin documentهایی را return میکند که مقدار فیلد آنها در آرایه مشخص شده وجود نداشته باشد. این فیلتر برای حذف مقادیر خاص از نتایج کاربرد دارد.
// Get all users with a name other than Kyle or John
db.users.find({ name: { $nin: ["Kyle", "John"] } })
$andاین فیلتر بررسی میکند که تمامی شروط موجود در آرایه صحیح باشند. بهطور پیشفرض، اگر چند جفت key-value را در یک آبجکت فیلتر قرار دهیم، MongoDB آنها را با منطق AND ترکیب میکند، بنابراین استفاده مستقیم از $and در بسیاری موارد ضروری نیست.
// Get all users with the name Kyle and the age 22
db.users.find({ $and: [{ name: "Kyle" }, { age: 22 }] })
// This is the same as db.users.find({ age: 22, name: "Kyle" })
$orاین فیلتر مشابه $and عمل میکند، با این تفاوت که بررسی میکند حداقل یکی از شروط موجود در آرایه صحیح باشد. فیلتر $or زمانی مفید است که بخواهیم documentهایی را که با یکی از چند شرط مختلف تطابق دارند، انتخاب کنیم.
// Get all users with the name Kyle or the age 22
db.users.find({ $or: [{ name: "Kyle" }, { age: 22 }] })
$notفیلتر $not معکوس شرط مشخص شده را اعمال میکند. به عبارتی، فقط documentهایی را برمیگرداند که با شرط داخلی مطابقت ندارند. البته در بسیاری از مواقع میتوان با ترکیب سایر فیلترها، به همان نتیجه بدون استفاده از $not رسید.
// Get all users with a name other than Kyle
db.users.find({ $name: { $not: { $eq: "Kyle" } } })
$existsاین فیلتر بر اساس وجود یا عدم وجود یک فیلد در document، عمل میکند. لازم است به این نکته دقت داشته باشیم که $exists فقط بررسی میکند که آیا فیلد مورد نظر تعریف شده است یا خیر؛ بنابراین اگر فیلدی وجود داشته باشد ولی مقدار آن null باشد، باز هم توسط $exists بهعنوان موجود شناسایی میشود.
// Get all users with a name field defined
db.users.find({ $name: { $exists: true } })
// Get all users without a name field
db.users.find({ $name: { $exists: false } })
$exprاین فیلتر امکان مقایسه بین چند فیلد مختلف در یک document را فراهم میکند. در واقع با استفاده از $expr میتوانیم منطق شرطی پیشرفتهتری را پیادهسازی کنیم که وابسته به مقایسه مقادیر درون یک document است، نه فقط مقایسه مقدار با یک مقدار ثابت.
// Get all users that have a balance greater than their debt
db.users.find({ $expr: { $gt: ["$balance", "$debt"] } })
پیشتر در بخش Update اشاره کردیم که در توابع update فقط مقادیر جدید فیلدها ارسال نمیشوند، بلکه میتوانیم گزینههای مختلفی را نیز برای کنترل دقیقتر بهروزرسانیها اعمال کنیم. در این بخش از آموزش MongoDB، تمام این گزینهها را بررسی میکنیم. همچنین امکان استفاده همزمان از چندین گزینه برای انجام بهروزرسانیهای پیچیده نیز وجود دارد.
$setاگر فقط میخواهیم مقدار یک فیلد را از مقدار فعلی به مقدار جدیدی تغییر دهیم، گزینه $set سادهترین و بهترین انتخاب است.
// Update the first user with an age of 12 to also have the name Kyle
db.users.updateOne({ age: 12 }, { $set: { name: "Kyle" } })
$incبرای افزایش یا کاهش مقدار عددی یک فیلد میتوانیم از گزینه $inc استفاده کنیم. این گزینه، عدد مشخص شده را به مقدار فعلی فیلد اضافه میکند (در صورت استفاده از عدد منفی، عمل کاهش انجام میشود).
// Update the first user with an age of 12 by subtracting 2 from its age
db.users.updateOne({ age: 12 }, { $inc: { age: -2 } })
$renameاین گزینه برای تغییر نام یک فیلد در document بهکار میرود.
// Rename the age field to years on all users
db.users.updateMany({}, { $rename: { age: "years" } })
$unsetبرای حذف کامل یک فیلد از document، میتوانیم از گزینه $unset استفاده کنیم. کافی است نام فیلد را به همراه یک رشته خالی به این گزینه ارسال کنیم.
// Remove the age field from all users with the age of 12
db.users.updateOne({ age: 12 }, { $unset: { age: "" } })
$pushبرای افزودن یک مقدار جدید به انتهای یک آرایه در یک document میتوانیم از $push استفاده کنیم. این گزینه زمانی کاربرد دارد که بخواهیم لیستی از آیتمها را بهتدریج گسترش دهیم.
// Add John to the friends array for all users
db.users.updateMany({}, { $push: { friends: "John" } })
$pullگزینه $pull برعکس $push عمل میکند؛ یعنی مقدار مشخص شده را از آرایه حذف میکند.
// Remove John from the friends array for all users
db.users.updateMany({}, { $pull: { friends: "John" } })
آخرین مفهومی که در مقاله آموزش MongoDB بررسی میکنیم، Read Modifierها هستند. این گزینهها را میتوانیم به انتهای هر عملیات Read اضافه کنیم تا کنترل بیشتری بر نتایج حاصل داشته باشیم. همچنین میتوانیم چندین modifier را بهصورت زنجیرهای نیز به کار ببریم.
$sortاین modifier، نتایج حاصل از عملیات خواندن را مرتب میکند.
1 را به فیلد میدهیم-1 را وارد میکنیم.در صورت مرتبسازی بر اساس چند فیلد، ترتیب آنها مطابق با ترتیبی خواهد بود که به تابع sort ارسال شدهاند.
// Get all users sorted by name in ascending order and then if any names are the same sort by age in descending order
db.users.find().sort({ name: 1, age: -1 })
$limitاین modifier تعداد documentهایی را که میخواهیم در خروجی مشاهده کنیم، محدود میکند.
// Get the first 2 users db.users.find().limit(2)
$skipاین گزینه، تعداد مشخصی از documentهای ابتدایی را از نتایج حذف میکند. ترکیب skip و limit برای پیادهسازی صفحهبندی بسیار پرکاربرد است.
// Get all users except the first 4 db.users.find().skip(4)
در این آموزش MongoDB تلاش کردیم تمامی مفاهیم مهم این پایگاه داده NoSQL، از ساختار کلی و اصطلاحات پایه گرفته تا متدهای CRUD، فیلترهای پیچیده، عملیات بهروزرسانی و دستورات خواندن پیشرفته را بررسی کنیم. با درک کامل این آموزش MongoDB، آماده استفاده حرفهای از MongoDB در پروژههای واقعی خواهیم بود.