MongoDB Fundamentals – DEV Community

Summarize this content to 400 words in Persian Lang
معماری MongoDB
MongoDB یک پایگاه داده محبوب NoSQL است که برای عملکرد بالا، در دسترس بودن بالا و مقیاس پذیری آسان طراحی شده است. این داده ها را در اسناد منعطف و JSON مانند ذخیره می کند و کار با داده های ساختاریافته، نیمه ساختاریافته و بدون ساختار را آسان می کند.
پایگاه داده: ظرفی برای مجموعه ها.
مجموعه: گروهی از اسناد MongoDB.
سند: مجموعه ای از جفت های کلید-مقدار (شبیه به اشیاء JSON).
عملیات CRUD
CRUD مخفف Create، Read، Update و Delete است. اینها عملیات اساسی برای تعامل با داده ها در MongoDB هستند.
ایجاد کنید
برای درج یک سند جدید در مجموعه، از insertOne() یا insertMany() روش ها
insertOne
db.collection(‘users’).insertOne({ name: ‘Alice’, age: 25 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
فیلد _id که به صورت خودکار ایجاد شده استاگر یک را مشخص نکنید _id در فیلد، MongoDB به طور خودکار یک شناسه منحصر به فرد برای هر سند ایجاد می کند. برای مشخص کردن خودتون _id مقدار، می توانید آن را در سند وارد کنید.
db.collection(‘users’).insertOne({ _id: 1, name: ‘Alice’, age: 25 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اطمینان حاصل کنید که به موارد تکراری رسیدگی کنید _id ارزش ها برای جلوگیری از تعارض
درج بسیاری
برای درج چندین سند، می توانید از insertMany() روش
db.collection(‘users’).insertMany([
{ name: ‘Alice’, age: 25 },
{ name: ‘Bob’, age: 30 }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اگر یکی از اسناد درج نشود، عملیات لغو می شود مگر اینکه شما آن را مشخص کنید ordered: false گزینه
به عنوان مثال اگر شما طرحواره وادیاسیون مانند این دارید. به Schema Validation بروید
db.createCollection(‘users’, {
validator: {
$jsonSchema: {
bsonType: ‘object’,
required: [‘name’, ‘age’],
properties: {
name: { bsonType: ‘string’ },
age: { bsonType: ‘int’ }
}
}
}
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
db.collection(‘users’).insertMany([
{ name: ‘Alice’, age: 25 },
{ name: ‘Bob’, age: 30 },
{ name: ‘Charlie’ } // missing age field
], { ordered: false });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
سند سوم نادیده گرفته می شود و دو سند اول درج می شود.
نتیجه خواهد بود
{
acknowledged: true,
insertedIds: {
‘0’: ObjectId(“…”), // ID for Alice
‘1’: ObjectId(“…”) // ID for Bob
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
بخوانید
برای خواندن اسناد از یک مجموعه، از find() روش متد find() چندین سند را بازیابی می کند و مکان نما را برمی گرداند که می تواند برای دسترسی به اسناد تکرار شود.
پیدا کردن
const cursor = db.collection(‘users’).find({ age: { $gte: 18 } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در Node.js می توانید مکان نما را با استفاده از آرایه تبدیل کنید toArray() روش
const docs = await cursor.toArray();
console.log(docs)
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نتیجه:
[{ “_id”: 1, “name”: “Alice”, “age”: 25 },
{ “_id”: 2, “name”: “Bob”, “age”: 30 }
]
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
findOne
برای بازیابی یک سند، می توانید از findOne() روش
db.collection(‘users’).findOne({ name: ‘Alice’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نتیجه:
{ “_id”: 1, “name”: “Alice”, “age”: 25 }
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
فرافکنی
می توانید با استفاده از پارامتر طرح ریزی مشخص کنید که کدام فیلدها در نتیجه گنجانده یا حذف شوند.
db.collection(‘users’).find({}, { projection: { name: 1, age: 1 } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نتیجه:
[{ “_id”: 1, “name”: “Alice”, “age”: 25 },
{ “_id”: 2, “name”: “Bob”, “age”: 30 }
]
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
استفاده از طرح ریزی یک عمل حیاتی است، به خصوص هنگام کار با مجموعه های بزرگ. این مقدار داده های منتقل شده از طریق شبکه را کاهش می دهد و عملکرد پرس و جو را بهبود می بخشد. همچنین از قرار گرفتن در معرض ناخواسته داده های حساس جلوگیری می کند.
به روز رسانی
برای به روز رسانی اسناد موجود، از updateOne() یا updateMany() روش ها
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $set: { age: 26 } });
db.collection(‘users’).updateMany({ city: ‘New York’ }, { $set: { city: ‘San Francisco’ } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هدف نتیجه
{
“acknowledged”: true,
“matchedCount”: 5,
“modifiedCount”: 5,
“upsertedId”: null,
“upsertedCount”: 0
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
بر اساس شی نتیجه شما می توانید پاسخ مناسب را برگردانید.
const result = await db.collection(‘users’).updateOne(
{ _id: userId },
{ $set: { age: 30 } }
);
if (result.matchedCount === 0) {
return { success: false, message: ‘No matching document found’ };
}
if (result.modifiedCount === 0) {
return { success: true, message: ‘Document already up-to-date’ };
}
return { success: true, message: ‘Document updated successfully’ };
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
ناراحت
اگر میخواهید سند جدیدی را در زمانی که هیچ سند منطبقی یافت نشد وارد کنید، میتوانید از آن استفاده کنید upsert گزینه
db.collection(‘users’).updateOne(
{ name: ‘Alice’ },
{ $set: { age: 26 }
},
{ upsert: true }
);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هدف نتیجه
{
“acknowledged”: true,
“matchedCount”: 1,
“modifiedCount”: 1,
“upsertedId”: ObjectId(“…”),
“upsertedCount”: 1
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
FindOneAndUpdate
این روش ترکیبی از findOne() و updateOne() به عنوان عملیات اتمی عملیات اتمی عملیاتی است که به صورت یک واحد کار انجام می شود. این به جلوگیری از شرایط مسابقه و اطمینان از سازگاری داده ها کمک می کند.
// Update or create a user profile
db.users.findOneAndUpdate(
{ email: “alice@example.com” },
{ $set: { age: 26 } },
{ returnDocument: ‘after’ }
);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هدف نتیجه
returnDocument می تواند “قبل” یا “بعد” برای بازگرداندن سند قبل یا بعد از به روز رسانی باشد.
{
“value”: {
“_id”: ObjectId(“5f7d3b1c8e1f9a1c9c8e1f9a”),
“email”: “alice@example.com”,
“age”: 26,
“name”: “Alice”
},
“lastErrorObject”: {
“updatedExisting”: true,
“n”: 1
},
“ok”: 1
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
به روز رسانی اپراتورها
// $set operator to update fields
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $set: { age: 26, city: ‘New York’ } });
// $set operator to update nested fields
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $set: { ‘address.city’: ‘New York’ } });
// $inc operator to increment a field
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $inc: { age: 1 } });
// $mul operator to multiply a field value
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $mul: { age: 2 } });
// $unset operator to remove a field
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $unset: { city: ” } });
// $rename operator to rename a field
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $rename: { city: ‘location’ } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حذف کنید
برای حذف اسناد از مجموعه، از deleteOne() یا deleteMany() روش ها
db.collection(‘users’).deleteOne({ name: ‘Alice’ }); // delete first matching document
db.collection(‘users’).deleteMany({ city: ‘New York’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هدف نتیجه
{
“acknowledged”: true,
“deletedCount”: 1
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
بر اساس شی نتیجه شما می توانید پاسخ مناسب را برگردانید.
const result = await db.collection(‘users’).deleteOne({ _id: userId });
if (result.deletedCount === 0) {
return { success: false, message: ‘No matching document found’ };
}
return { success: true, message: ‘Document deleted successfully’ };
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
از اطلاعات خود نسخه پشتیبان تهیه کنید: قبل از حذف اسناد، مطمئن شوید که از اطلاعات خود نسخه پشتیبان تهیه کرده اید تا از از دست رفتن تصادفی داده ها جلوگیری شود.
حذف نرم: به جای حذف دائمی اسناد، می توانید با افزودن a آنها را به عنوان حذف شده علامت گذاری کنید deletedAt زمینه این به شما امکان می دهد داده ها را برای اهداف ممیزی یا بازیابی نگهداری کنید.
db.collection(‘users’).updateOne(
{ _id: userId },
{ $set: { deletedAt: new Date() } }
);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
فیلتر دقیق: هنگام حذف اسناد، از یک فیلتر دقیق استفاده کنید تا از حذف ناخواسته اسناد بیشتر از آنچه در نظر گرفته شده است جلوگیری کنید. می توانید از find() برای پیش نمایش اسنادی که قبل از اجرای عملیات حذف حذف می شوند استفاده کنید.
اعتبار سنجی طرحواره
مدل در مقابل الگوی DAO
نمونه همه مدل
الگوی مدل
داده ها و رفتار را در بر می گیرد. این ممکن است شامل اعتبار سنجی، منطق تجاری و عملیات پایگاه داده باشد. این می تواند به “چاق” اما اجرای ساده تر تبدیل شود.
این نمونه ای از ساختار فایل ها برای الگوی مدل است
src/
├── config/
│ └── database.js
│
├── models/
│ └── todo.model.js
│
├── controllers/
│ └── todo.controller.js
│
├── routes/
│ └── todo.routes.js
│
├── middleware/
│ └── auth.middleware.js
│
└── app.js
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اگر مدل خیلی پیچیده شد، می توانید آن را به چندین فایل یا کلاس تقسیم کنید. به عنوان مثال، می توانید فایل های جداگانه ای برای اعتبار سنجی، منطق تجاری و عملیات پایگاه داده داشته باشید.
پیکربندی داده ها
// src/config/database.js
const { MongoClient } = require(‘mongodb’);
const dbConfig = {
url: process.env.MONGODB_URI || ‘mongodb://localhost:27017’,
dbName: ‘todoapp’
};
let db = null;
const connectDB = async () => {
try {
const client = await MongoClient.connect(dbConfig.url, {
useUnifiedTopology: true
});
db = client.db(dbConfig.dbName);
console.log(‘Connected to MongoDB successfully’);
return db;
} catch (error) {
console.error(‘MongoDB connection error:’, error);
process.exit(1);
}
};
const getDB = () => {
if (!db) {
throw new Error(‘Database not initialized’);
}
return db;
};
module.exports = { connectDB, getDB };
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
مدل
فایل مدل شامل ساختار داده، اعتبارسنجی و عملیات پایگاه داده برای یک آیتم انجام می شود. این شامل داده ها و رفتار مربوط به کارها است.
// src/models/todo.model.js
onst { ObjectId } = require(‘mongodb’);
const { getDB } = require(‘../config/database’);
const COLLECTION_NAME = ‘todos’;
// Validation functions
const validateTodoData = (todoData) => {
const errors = [];
if (!todoData.title) {
errors.push(‘Title is required’);
} else if (todoData.title.length < 3) {
errors.push(‘Title must be at least 3 characters long’);
}
if (todoData.status && ![‘pending’, ‘in-progress’, ‘completed’].includes(todoData.status)) {
errors.push(‘Invalid status. Must be pending, in-progress, or completed’);
}
if (todoData.dueDate && new Date(todoData.dueDate) < new Date()) {
errors.push(‘Due date cannot be in the past’);
}
if (todoData.priority && ![‘low’, ‘medium’, ‘high’].includes(todoData.priority)) {
errors.push(‘Invalid priority. Must be low, medium, or high’);
}
return errors;
};
const todoModel = {
async create(todoData) {
const errors = validateTodoData(todoData);
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(‘, ‘)}`);
}
const db = getDB();
const todo = {
…todoData,
title: todoData.title.trim(),
status: todoData.status || ‘pending’,
priority: todoData.priority || ‘medium’,
createdAt: new Date(),
updatedAt: new Date(),
completedAt: null
};
const result = await db.collection(COLLECTION_NAME).insertOne(todo);
return { …todo, _id: result.insertedId };
},
async findById(id) {
if (!ObjectId.isValid(id)) {
throw new Error(‘Invalid todo ID’);
}
const db = getDB();
const todo = await db.collection(COLLECTION_NAME).findOne({
_id: new ObjectId(id)
});
if (!todo) {
throw new Error(‘Todo not found’);
}
return todo;
},
async find(query = {}, options = {}) {
const db = getDB();
const {
page = 1,
limit = 10,
sortBy = ‘createdAt’,
sortOrder = -1
} = options;
if (page < 1 || limit < 1) {
throw new Error(‘Invalid pagination parameters’);
}
const skip = (page – 1) * limit;
const sortOptions = { [sortBy]: sortOrder };
// Apply filters
const filters = { …query };
if (filters.priority) {
if (![‘low’, ‘medium’, ‘high’].includes(filters.priority)) {
throw new Error(‘Invalid priority filter’);
}
}
if (filters.status) {
if (![‘pending’, ‘in-progress’, ‘completed’].includes(filters.status)) {
throw new Error(‘Invalid status filter’);
}
}
const [todos, totalCount] = await Promise.all([
db.collection(COLLECTION_NAME)
.find(filters)
.sort(sortOptions)
.skip(skip)
.limit(limit)
.toArray(),
db.collection(COLLECTION_NAME)
.countDocuments(filters)
]);
return {
todos,
pagination: {
total: totalCount,
page,
limit,
pages: Math.ceil(totalCount / limit)
}
};
},
async update(id, updateData) {
if (!ObjectId.isValid(id)) {
throw new Error(‘Invalid todo ID’);
}
const errors = validateTodoData(updateData);
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(‘, ‘)}`);
}
const db = getDB();
const existingTodo = await this.findById(id);
// Business logic for status changes
if (updateData.status === ‘completed’ && existingTodo.status !== ‘completed’) {
updateData.completedAt = new Date();
}
if (updateData.status && updateData.status !== ‘completed’) {
updateData.completedAt = null;
}
const result = await db.collection(COLLECTION_NAME).findOneAndUpdate(
{ _id: new ObjectId(id) },
{
$set: {
…updateData,
updatedAt: new Date()
}
},
{ returnDocument: ‘after’ }
);
if (!result.value) {
throw new Error(‘Todo not found’);
}
return result.value;
},
async delete(id) {
if (!ObjectId.isValid(id)) {
throw new Error(‘Invalid todo ID’);
}
const db = getDB();
const result = await db.collection(COLLECTION_NAME).deleteOne({
_id: new ObjectId(id)
});
if (result.deletedCount === 0) {
throw new Error(‘Todo not found’);
}
return true;
},
// Additional business logic methods
async markAsComplete(id) {
return await this.update(id, {
status: ‘completed’
});
},
async findOverdue() {
const db = getDB();
return await db.collection(COLLECTION_NAME).find({
dueDate: { $lt: new Date() },
status: { $ne: ‘completed’ }
}).toArray();
}
};
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نمونه همه DAO
الگوی DAO
منطق دسترسی به داده ها از منطق تجاری جدا شده است.
لایه سرویس برای منطق تجاری و لایه DAO برای دسترسی به داده ها.
برای آزمایش با تمسخر لایه دسترسی به داده بهتر است.
تزریق وابستگی را بهتر پشتیبانی می کند.
پیاده سازی پیچیده تر و تکرار کد.
src/
├── config/
│ └── database.js # Database connection configuration
│
├── models/
│ └── todo.entity.js # Data structure/schema definition
│
├── daos/
│ └── todo.dao.js # Data Access Object – handles database operations
│
├── services/
│ └── todo.service.js # Business logic layer
│
├── controllers/
│ └── todo.controller.js # Request handling & response formatting
│
├── routes/
│ └── todo.routes.js # Route definitions
│
└── app.js # Application entry point
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پیکربندی پایگاه داده
// src/config/database.js
const { MongoClient } = require(‘mongodb’);
class Database {
constructor(config) {
this.config = {
url: config.url || process.env.MONGODB_URI || ‘mongodb://localhost:27017’,
dbName: config.dbName || process.env.DB_NAME || ‘todoapp’,
options: {
useUnifiedTopology: true,
…config.options
}
};
this.client = null;
this.db = null;
}
async connect() {
try {
this.client = await MongoClient.connect(this.config.url, this.config.options);
this.db = this.client.db(this.config.dbName);
console.log(‘Connected to MongoDB successfully’);
return this.db;
} catch (error) {
console.error(‘MongoDB connection error:’, error);
throw new DatabaseError(‘Failed to connect to database’, error);
}
}
async disconnect() {
try {
if (this.client) {
await this.client.close();
this.client = null;
this.db = null;
console.log(‘Disconnected from MongoDB’);
}
} catch (error) {
console.error(‘MongoDB disconnection error:’, error);
throw new DatabaseError(‘Failed to disconnect from database’, error);
}
}
getDB() {
if (!this.db) {
throw new DatabaseError(‘Database not initialized. Call connect() first.’);
}
return this.db;
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
موجودیت
// src/models/todo.entity.js
class Todo {
static STATUS = {
PENDING: ‘pending’,
IN_PROGRESS: ‘in-progress’,
COMPLETED: ‘completed’
};
static PRIORITY = {
LOW: ‘low’,
MEDIUM: ‘medium’,
HIGH: ‘high’
};
constructor(data = {}) {
this._id = data._id || null;
this.title = data.title || ”;
this.description = data.description || ”;
this.status = data.status || Todo.STATUS.PENDING;
this.priority = data.priority || Todo.PRIORITY.MEDIUM;
this.dueDate = data.dueDate ? new Date(data.dueDate) : null;
this.createdAt = data.createdAt ? new Date(data.createdAt) : new Date();
this.updatedAt = data.updatedAt ? new Date(data.updatedAt) : new Date();
this.completedAt = data.completedAt ? new Date(data.completedAt) : null;
this.tags = Array.isArray(data.tags) ? […data.tags] : [];
this.assignedTo = data.assignedTo || null;
}
validate() {
const errors = [];
if (!this.title?.trim()) {
errors.push(‘Title is required’);
} else if (this.title.trim().length < 3) {
errors.push(‘Title must be at least 3 characters long’);
}
if (this.status && !Object.values(Todo.STATUS).includes(this.status)) {
errors.push(`Invalid status. Must be one of: ${Object.values(Todo.STATUS).join(‘, ‘)}`);
}
if (this.dueDate) {
if (!(this.dueDate instanceof Date) || isNaN(this.dueDate.getTime())) {
errors.push(‘Invalid due date format’);
} else if (this.dueDate < new Date()) {
errors.push(‘Due date cannot be in the past’);
}
}
if (this.priority && !Object.values(Todo.PRIORITY).includes(this.priority)) {
errors.push(`Invalid priority. Must be one of: ${Object.values(Todo.PRIORITY).join(‘, ‘)}`);
}
if (this.tags && !Array.isArray(this.tags)) {
errors.push(‘Tags must be an array’);
}
return errors;
}
isOverdue() {
return this.dueDate && this.dueDate < new Date() && this.status !== Todo.STATUS.COMPLETED;
}
toJSON() {
return {
_id: this._id,
title: this.title,
description: this.description,
status: this.status,
priority: this.priority,
dueDate: this.dueDate,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
completedAt: this.completedAt,
tags: this.tags,
assignedTo: this.assignedTo
};
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
کلاس خطاهای اعتبارسنجی
// src/errors/index.js
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = ‘ValidationError’;
this.status = 400;
}
}
class DatabaseError extends Error {
constructor(message, originalError = null) {
super(message);
this.name = ‘DatabaseError’;
this.status = 500;
this.originalError = originalError;
}
}
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = ‘NotFoundError’;
this.status = 404;
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
DAO
مبتنی بر کلاس با تزریق سازنده
این سبک یک کلاس BaseDAO را تعریف می کند که یک رابط تمیز و قابل استفاده مجدد با متدهایی مانند findOne، insertOne و updateOne ارائه می کند. هر DAO (مانند TodoDAO) BaseDAO را گسترش می دهد و در یک نام مجموعه خاص عبور می کند
جوانب مثبت:
تمیز و قابل استفاده مجدد: BaseDAO یک پایه خوب است، به خصوص اگر چندین DAO دارید که از ساختارهای مشابه پیروی می کنند.
اعتبار سنجی سازنده: اطمینان حاصل میکند که db و collectionName ارائه شدهاند، که کمک میکند مشکلات را زودتر شناسایی کنید.
خوانایی و قابلیت نگهداری: عملیات CRUD به خوبی کپسوله شده است و می تواند به راحتی در DAO های مختلف با گسترش BaseDAO دوباره استفاده شود.
معایب:
کد دیگ بخار بیشتر
هر DAO باید BaseDAO را گسترش دهد، که ممکن است انعطاف پذیری را برای مواردی که یک DAO ممکن است نیاز به مقداردهی اولیه اضافی یا یک ساختار منحصر به فرد داشته باشد، محدود کند.
// src/daos/base.dao.js
class BaseDAO {
constructor(db, collectionName) {
if (!db) {
throw new Error(‘Database connection is required’);
}
if (!collectionName) {
throw new Error(‘Collection name is required’);
}
this.db = db;
this.collection = this.db.collection(collectionName);
}
async findOne(filter) {
try {
return await this.collection.findOne(filter);
} catch (error) {
throw new DatabaseError(‘Database query failed’, error);
}
}
async find(filter = {}, options = {}) {
try {
return await this.collection.find(filter, options).toArray();
} catch (error) {
throw new DatabaseError(‘Database query failed’, error);
}
}
async insertOne(data) {
try {
return await this.collection.insertOne(data);
} catch (error) {
throw new DatabaseError(‘Database insert failed’, error);
}
}
async updateOne(filter, update, options = {}) {
try {
return await this.collection.updateOne(filter, update, options);
} catch (error) {
throw new DatabaseError(‘Database update failed’, error);
}
}
async deleteOne(filter) {
try {
return await this.collection.deleteOne(filter);
} catch (error) {
throw new DatabaseError(‘Database delete failed’, error);
}
}
}
// src/daos/todo.dao.js
class TodoDAO extends BaseDAO {
constructor(db) {
super(db, ‘todos’);
}
async create(todoData) {
const todo = new Todo(todoData);
const errors = todo.validate();
if (errors.length > 0) {
throw new ValidationError(errors.join(‘, ‘));
}
const todoToInsert = {
…todo.toJSON(),
title: todo.title.trim(),
createdAt: new Date(),
updatedAt: new Date()
};
try {
const result = await this.insertOne(todoToInsert);
return new Todo({ …todoToInsert, _id: result.insertedId });
} catch (error) {
throw new DatabaseError(‘Failed to create todo’, error);
}
}
async findById(id) {
if (!ObjectId.isValid(id)) {
throw new ValidationError(‘Invalid todo ID’);
}
const todo = await this.findOne({ _id: new ObjectId(id) });
if (!todo) {
throw new NotFoundError(‘Todo not found’);
}
return new Todo(todo);
}
async find(query = {}, options = {}) {
const {
page = 1,
limit = 10,
sortBy = ‘createdAt’,
sortOrder = -1,
status,
priority,
searchTerm,
fromDate,
toDate,
tags
} = options;
if (page < 1 || limit < 1) {
throw new ValidationError(‘Invalid pagination parameters’);
}
const filter = this._buildFilter({
…query,
status,
priority,
searchTerm,
fromDate,
toDate,
tags
});
const skip = (page – 1) * limit;
const sortOptions = { [sortBy]: sortOrder };
try {
const [todos, totalCount] = await Promise.all([
this.collection
.find(filter)
.sort(sortOptions)
.skip(skip)
.limit(limit)
.toArray(),
this.collection.countDocuments(filter)
]);
return {
todos: todos.map(todo => new Todo(todo)),
pagination: {
total: totalCount,
page,
limit,
pages: Math.ceil(totalCount / limit)
}
};
} catch (error) {
throw new DatabaseError(‘Failed to fetch todos’, error);
}
}
async update(id, updateData) {
const existingTodo = await this.findById(id);
const updatedTodo = new Todo({
…existingTodo,
…updateData,
_id: existingTodo._id,
updatedAt: new Date()
});
const errors = updatedTodo.validate();
if (errors.length > 0) {
throw new ValidationError(errors.join(‘, ‘));
}
const result = await this.updateOne(
{ _id: new ObjectId(id) },
{ $set: updatedTodo.toJSON() },
{ returnDocument: ‘after’ }
);
if (result.matchedCount === 0) {
throw new NotFoundError(‘Todo not found’);
}
return updatedTodo;
}
async delete(id) {
if (!ObjectId.isValid(id)) {
throw new ValidationError(‘Invalid todo ID’);
}
const result = await this.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 0) {
throw new NotFoundError(‘Todo not found’);
}
return true;
}
_buildFilter(options) {
const filter = {};
if (options.status) {
if (!Object.values(Todo.STATUS).includes(options.status)) {
throw new ValidationError(‘Invalid status filter’);
}
filter.status = options.status;
}
if (options.priority) {
if (!Object.values(Todo.PRIORITY).includes(options.priority)) {
throw new ValidationError(‘Invalid priority filter’);
}
filter.priority = options.priority;
}
if (options.searchTerm) {
filter.$or = [
{ title: { $regex: options.searchTerm, $options: ‘i’ } },
{ description: { $regex: options.searchTerm, $options: ‘i’ } }
];
}
if (options.fromDate || options.toDate) {
filter.dueDate = {};
if (options.fromDate) {
filter.dueDate.$gte = new Date(options.fromDate);
}
if (options.toDate) {
filter.dueDate.$lte = new Date(options.toDate);
}
}
if (options.tags && Array.isArray(options.tags)) {
filter.tags = { $all: options.tags };
}
return filter;
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
روش استاتیک با اتصال تزریقی
استفاده ساده شده: روشهای استاتیک نیاز به نمونهسازی کلاس را برطرف میکند و سربار حافظه را کاهش میدهد.
چالش های تست: روشهای استاتیک با تزریق وابستگی انعطافپذیری کمتری دارند و باعث میشوند که تمسخرها و تستها پیچیدهتر شوند.
قابلیت استفاده مجدد محدود: روشهای استاتیک از وراثت پشتیبانی نمیکنند، بنابراین متمرکز کردن منطق مشترک بین DAOها دشوارتر است.
import { ObjectId } from ‘mongodb’;
let todos;
export default class TodoDAO {
static async injectDB(conn) {
if (todos) return;
try {
todos = await conn.db(process.env.DB_NAME).collection(‘todos’);
} catch (error) {
console.error(`Unable to establish collection handles in TodoDAO: ${error}`);
}
}
static async create(todoData) {
// Initialize and validate the Todo, build the data object, and insert into the collection
}
static async findById(id) {
// Validate ID, query by `_id`, throw `NotFoundError` if not found
}
static async find(query = {}, options = {}) {
// Construct filter, pagination, and sort options, execute the find query with pagination
}
static async update(id, updateData) {
// Fetch the todo, validate and update with `updateOne`
}
static async delete(id) {
// Validate ID, delete by `_id`, and handle `NotFoundError`
}
static _buildFilter(options) {
// Generate the filter object for queries based on status, priority, search terms, etc.
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
خدمات
// src/services/todo.service.js
class TodoService {
constructor(todoDAO) {
if (!todoDAO) {
throw new Error(‘TodoDAO is required’);
}
this.todoDAO = todoDAO;
}
async createTodo(todoData) {
return await this.todoDAO.create(todoData);
}
async getTodoById(id) {
return await this.todoDAO.findById(id);
}
async updateTodo(id, updateData) {
return await this.todoDAO.update(id, updateData);
}
async deleteTodo(id) {
return await this.todoDAO.delete(id);
}
async updateTodoStatus(id, status) {
const todo = await this.todoDAO.findById(id);
const updates = {
status,
updatedAt: new Date()
};
if (status === Todo.STATUS.COMPLETED && todo.status !== Todo.STATUS.COMPLETED) {
updates.completedAt = new Date();
} else if (status !== Todo.STATUS.COMPLETED && todo.status === Todo.STATUS.COMPLETED) {
updates.completedAt = null;
}
return await this.todoDAO.update(id, updates);
}
async findTodos(options = {}) {
return await this.todoDAO.find({}, options);
}
async findOverdueTodos() {
const now = new Date();
return await this.todoDAO.find({
dueDate: { $lt: now },
status: { $ne: Todo.STATUS.COMPLETED }
});
}
async findTodosByPriority(priority) {
return await this.todoDAO.find({ priority });
}
async assignTodo(id, userId) {
return await this.todoDAO.update(id, {
assignedTo: userId,
updatedAt: new Date()
});
}
async addTags(id, tags) {
const todo = await this.todoDAO.findById(id);
const uniqueTags = […new Set([…todo.tags, …tags])];
return await this.todoDAO.update(id, { tags: uniqueTags });
}
async removeTags(id, tags) {
const todo = await this.todoDAO.findById(id);
const updatedTags = todo.tags.filter(tag => !tags.includes(tag));
return await this.todoDAO.update(id, { tags: updatedTags });
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نمایه سازی
نمایه سازی در MongoDB عملکرد پرس و جو را با ایجاد ساختارهای داده کارآمد برای بازیابی سریعتر بهبود می بخشد.
شاخص تک فیلد: نمایه در یک فیلد واحد از یک سند.
شاخص مرکب: فهرست در چندین فیلد.
شاخص چند کلیدی: نمایه در فیلدهای آرایه.
فهرست متن: از عبارت های جستجوی متنی در محتوای رشته پشتیبانی می کند.
شاخص تک فیلد
برای ایجاد ایندکس، از createIndex() روش
db.collection(‘users’).createIndex({ name: 1 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
شاخص مرکب
یک شاخص ترکیبی در MongoDB یک نمایه در چندین فیلد در یک سند است.
db.collection(‘users’).createIndex({ name: 1, age: 1, city: 1, country: 1, hobbies: 1 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پیشوندهای یک نمایه مرکب را می توان برای برآوردن پرس و جوهایی استفاده کرد که با فیلدهای فهرست از چپ به راست مطابقت دارند.
db.collection(‘users’).find({ name: ‘Alice’, age: 25 });
db.collection(‘users’).find({ name: ‘Alice’, age: 25, city: ‘New York’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
شاخص چند کلیدی
یک شاخص چند کلیدی در MongoDB یک شاخص در یک فیلد آرایه است.
db.collection(‘users’).createIndex({ hobbies: 1 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
محدودیت های شاخص های چند کلیدی
اگر بیش از یک فیلد یک آرایه باشد، نمی توانید یک شاخص ترکیبی ایجاد کنید.
db.collection(‘users’).createIndex({ “hobbies”: 1, “tags”: 1 }); // Not allowed if both hobbies and tags are arrays
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
فهرست متن
فهرست های متنی در MongoDB راهی برای انجام پرس و جوهای جستجوی متنی در محتوای رشته ارائه می دهند. هر مجموعه می تواند حداکثر یک فهرست متنی داشته باشد.
db.products.createIndex({
name: “text”,
description: “text”,
tags: “text”
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
// Sample Data
db.products.insertMany([
{
name: “MacBook Pro 16-inch”,
description: “Powerful laptop for developers”,
tags: [“apple”, “laptop”, “computer”]
},
{
name: “iPhone 15 Pro”,
description: “Latest smartphone with great camera”,
tags: [“apple”, “smartphone”, “mobile”]
},
{
name: “Gaming Laptop ROG”,
description: “High-performance gaming laptop”,
tags: [“asus”, “laptop”, “gaming”]
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تطابق دقیق کلمات
db.products.find({ $text: { $search: “laptop” }}); // Will find MacBook and ROG
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
چند کلمه (OR)
// By default, multiple words are treated as logical OR
// Will find documents containing “laptop” or “gaming”
db.products.find({ $text: { $search: “laptop gaming” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
چند کلمه (منطقی و)
// Using the plus sign to ensure both terms are present
// Will find documents containing both “laptop” and “gaming”
db.products.find({ $text: { $search: “+laptop +gaming” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
عبارات دقیق
// Using double quotes to search for an exact phrase
// Will find documents containing the exact phrase “gaming laptop”
db.products.find({ $text: { $search: “\”gaming laptop\”” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حذف کلماتdb.products.find({ $text: { $search: “laptop -gaming” }}); // لپ تاپ اما نه گیمینگ
مسابقات بدون حروف بزرگ
// Text search is case-insensitive by default
// Will find all documents containing “laptop” regardless of case
db.products.find({ $text: { $search: “LAPTOP” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
دیاکریتیک غیر حساس
// Text search ignores diacritic marks by default
// Will match “cafe” and “café”
db.products.find({ $text: { $search: “café” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
مطابقت جزئی کلمه
// Searching for “lap” won’t find “laptop”
db.products.find({ $text: { $search: “lap” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
جستجوهای حروف عامیانه
// Wildcards are not supported in text search
db.products.find({ $text: { $search: “lap*” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تطبیق فازی (اشتباهات تایپی)
// Regular expressions cannot be used inside $text search
db.products.find({ $text: { $search: “/lap.*/” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
عبارات بولی پیچیده
// Nested boolean logic is not supported in $text search
db.products.find({ $text: { $search: “(laptop AND gaming) OR (smartphone AND camera)” }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
جستجوهای حساس به حروف کوچک و بزرگ
// Cannot perform case-sensitive searches using $text
db.products.find({ $text: { $search: “MacBook”, caseSensitive: true }});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
جستجوی اطلس
Atlas Search یک سرویس جستجوی کاملاً مدیریت شده است که به شما امکان می دهد قابلیت های جستجوی سریع، مرتبط و متن کامل را در بالای داده های MongoDB خود ایجاد کنید. در مقایسه با فهرستهای متنی، جستجوی اطلس ویژگیهای پیشرفتهتری مانند جستجوی وجهی، تطبیق فازی و امتیازدهی مرتبط را فراهم میکند.
// Basic Atlas Search Setup
db.products.createSearchIndex({
“mappings”: {
“dynamic”: true,
“fields”: {
“name”: {
“type”: “string”,
“analyzer”: “lucene.standard”
},
“description”: {
“type”: “string”,
“analyzer”: “lucene.english”
}
}
}
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تطبیق فازی – غلط املایی و غلط املایی را کنترل می کند
db.products.aggregate([
{
$search: {
text: {
query: “labtop”, // Will match “laptop”
path: “name”,
fuzzy: { maxEdits: 1 }
}
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تکمیل خودکار – پیشنهادات در زمان واقعی
db.products.aggregate([
{
$search: {
autocomplete: {
query: “mac”, // Will suggest “macbook”, “machine”, etc.
path: “name”
}
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
امتیازدهی سفارشی – ارتباط را افزایش دهید
می تواند ارتباط برخی اسناد را بر اساس معیارهای خاص افزایش دهد.
db.products.aggregate([
{
$search: {
compound: {
must: [
{ text: { query: “laptop”, path: “name” } }
],
should: [
{
text: {
query: “gaming”,
path: “category”,
score: { boost: { value: 2.0 } } // Boost gaming laptops
}
}
]
}
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اسناد باید حاوی “لپ تاپ” در قسمت نام باشند
اسناد حاوی “بازی” در قسمت توضیحات تقویت می شوند.
پشتیبانی چند زبانه
db.products.aggregate([
{
$search: {
text: {
query: “ordinateur portable”, // French for “laptop”
path: “description”,
analyzer: “lucene.french”
}
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
جستجوی وجهی – نتایج را فیلتر کنید
جستجوی وجهی به کاربران اجازه می دهد تا نتایج جستجو را بر اساس دسته بندی ها، محدوده قیمت و سایر ویژگی ها فیلتر کنند.
db.products.aggregate([
{
$searchMeta: {
facet: {
operator: { text: { query: “laptop”, path: “description” } },
facets: {
categories: { type: “string”, path: “category” },
priceRanges: {
type: “number”,
path: “price”,
boundaries: [500, 1000, 2000]
}
}
}
}
}
]);
**Facets:**
– categoryFacet: Groups results by the category field.
– priceFacet: Groups results into price ranges defined by the boundaries.
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
مبادلات
علاوه بر مزایا، جستجوی اطلس در مقایسه با نمایههای متنی سنتی دارای برخی معاوضهها نیز است.
به خوشه های M10+ نیاز دارد
هزینه اضافی
راه اندازی پیچیده تر
استفاده بیشتر از منابع
عملیات آرایه
MongoDB از انواع عملیات آرایه برای کار با آرایه ها در اسناد پشتیبانی می کند.
افزودن عناصر به آرایه
برای افزودن عناصر به آرایه در یک سند، از $push اپراتور
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $push: { hobbies: ‘Reading’ } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پرس و جو آرایه ها با $elemMatch
با توجه به مجموعه ای از دانش آموزان با نمرات در موضوعات مختلف:
{
“_id”: ObjectId(“64d39a7a8b0e8c284a2c1234”),
“name”: “Alice”,
“scores”: [
{ “subject”: “Math”, “score”: 95 },
{ “subject”: “English”, “score”: 88 }
]
},
{
“_id”: ObjectId(“64d39a808b0e8c284a2c1235”),
“name”: “Bob”,
“scores”: [
{ “subject”: “Math”, “score”: 78 },
{ “subject”: “English”, “score”: 92 }
]
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پرس و جو با $elemMatch:
برای یافتن دانشآموزانی که به طور خاص در ریاضی نمره 90 کسب کردهاند، نیاز داریم $elemMatch:
db.students.find({
scores: {
$elemMatch: { subject: “Math”, score: { $gt: 90 } }
}
})
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نتیجه: این فقط آلیس را برمی گرداند، زیرا او تنها دانش آموز با نمره بالای 90 در درس “ریاضی” است. $elemMatch آن را تضمین می کند همه شرایط درون عنصر آرایه باید برآورده شود.
بدون $elemMatch – این امر هم آلیس و هم باب را برمی گرداند، زیرا هر دو در دروس مختلف و نه لزوماً در درس “ریاضی” نمرات بالای 90 دارند.
اضافه کردن منحصر به فرد – $addToSet
را $addToSet عملگر در MongoDB برای افزودن عناصر به آرایه تنها در صورتی استفاده می شود که از قبل وجود نداشته باشند. این از ورودهای تکراری در آرایه جلوگیری می کند.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $addToSet: { hobbies: ‘Reading’ } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
چندگانه اضافه کنید $push و $each
را $push عملگر در MongoDB برای افزودن عناصر به آرایه استفاده می شود. را $each modifier به شما اجازه می دهد چندین عنصر را به آرایه اضافه کنید.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $push: { hobbies: { $each: [‘Reading’, ‘Swimming’] } } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
افزودن مرتب شده – $push و $sort
را $push عملگر در MongoDB برای افزودن عناصر به آرایه استفاده می شود. را $sort modifier به شما اجازه می دهد تا عناصر آرایه را مرتب کنید.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $push: { scores: { $each: [85, 90], $sort: -1 } } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
فشار و مرتب سازی آرایه از اشیاء بر اساس یک فیلد خاص
db.collection(‘users’).updateOne(
{ name: ‘Alice’ },
{
$push: {
scores: {
$each: [
{ score: 85, date: “2023-03-01” },
{ score: 90, date: “2023-04-01” }
],
$sort: { date: -1 }
}
}
}
);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
افزودن محدود – $push و $slice
را $push عملگر در MongoDB برای افزودن عناصر به آرایه استفاده می شود. را $slice اصلاح کننده به شما امکان می دهد تعداد عناصر موجود در آرایه را محدود کنید.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $push: { scores: { $each: [85, 90], $slice: -3 } } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اگر آلیس در حال حاضر یک آرایه نمرات مانند [70, 75, 80]، این کوئری 85 و 90 را فشار می دهد و آن را ایجاد می کند [70, 75, 80, 85, 90]. سپس $slice: -3 آن را به 3 عنصر آخر برش میدهد و در نتیجه [80, 85, 90].
حذف عناصر از یک آرایه
برای حذف عناصر از آرایه در یک سند، از $pull اپراتور
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $pull: { hobbies: ‘Reading’ } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اولین یا آخرین عنصر را حذف کنید – $pop
را $pop عملگر در MongoDB برای حذف اولین یا آخرین عنصر از یک آرایه استفاده می شود.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $pop: { hobbies: 1 } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حذف چندگانه – $pullAll
را $pullAll عملگر در MongoDB برای حذف تمام رخدادهای مقادیر مشخص شده از یک آرایه استفاده می شود.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $pullAll: { hobbies: [‘Reading’, ‘Swimming’] } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حذف چندگانه – $pull و $in
را $pull عملگر در MongoDB برای حذف عناصر از یک آرایه استفاده می شود. را $in modifier به شما امکان می دهد چندین مقدار را برای حذف مشخص کنید.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $pull: { hobbies: { $in: [‘Reading’, ‘Swimming’] } } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حذف شرط – $pull و $gt
را $pull عملگر در MongoDB برای حذف عناصر از یک آرایه استفاده می شود. را $gt modifier به شما اجازه می دهد تا یک شرط برای حذف عناصر مشخص کنید.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $pull: { scores: { $gt: 85 } } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حذف نه برابر – $pull و $ne
را $pull عملگر در MongoDB برای حذف عناصر از یک آرایه استفاده می شود. را $ne modifier به شما اجازه می دهد تا یک شرط برای حذف عناصری که برابر با یک مقدار نیستند، تعیین کنید.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $pull: { scores: { $ne: 85 } } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
شرایط به روز رسانی – $set و $
را $set عملگر در MongoDB برای به روز رسانی فیلدهای یک سند استفاده می شود. را $ عملگر موقعیتی به شما امکان می دهد اولین عنصری را که با یک شرط در یک آرایه مطابقت دارد به روز کنید.
db.collection(‘users’).updateOne({ name: ‘Alice’, ‘scores.subject’: ‘Math’ }, { $set: { ‘scores.$.score’: 90 } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
به روز رسانی همه – $[]
را $[] عملگر در MongoDB برای به روز رسانی تمام عناصر یک آرایه که با یک شرط مطابقت دارند استفاده می شود.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $set: { ‘scores.$[].score’: 90 } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
برای افزایش یک فیلد در تمام عناصر یک آرایه، می توانید از $[] اپراتور با $inc اپراتور
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $inc: { ‘scores.$[].score’: 5 } });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
به روز رسانی فیلتر شده – $[]
را $[] عملگر در MongoDB برای به روز رسانی عناصر در یک آرایه که با یک شرط مطابقت دارند استفاده می شود.
db.collection(‘users’).updateOne({ name: ‘Alice’ }, { $set: { ‘scores.$[elem].score’: 90 } }, { arrayFilters: [{ ‘elem.subject’: ‘Math’ }] });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تجمیع
عملیات تجمیع، سوابق داده ها را پردازش کرده و نتایج محاسبه شده را برمی گرداند. Aggregation به شما امکان می دهد پردازش و تبدیل داده های پیچیده را انجام دهید.
خط لوله تجمع
چارچوب تجمیع در MongoDB از یک رویکرد خط لوله استفاده می کند، که در آن چندین مرحله اسناد را تغییر می دهد.
db.collection(‘orders’).aggregate([
{ $match: { status: ‘A’ } },
{ $group: { _id: ‘$cust_id’, total: { $sum: ‘$amount’ } } },
{ $sort: { total: -1 } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
به مجموعه ها بپیوندید
MongoDB از اتصالات مانند پایگاه داده های رابطه ای پشتیبانی نمی کند. در عوض، شما می توانید استفاده کنید $lookup عملگر برای انجام یک اتصال بیرونی سمت چپ بین دو مجموعه.
db.collection(‘orders’).aggregate([
{
$lookup: {
from: ‘customers’,
localField: ‘cust_id’,
foreignField: ‘_id’,
as: ‘customer’
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
باز کردن آرایه ها
را $unwind عملگر در MongoDB برای تجزیه یک فیلد آرایه به چندین سند استفاده می شود.
db.collection(‘orders’).aggregate([
{ $unwind: ‘$items’ }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
باز کردن و گروه
db.collection(‘orders’).aggregate([
{ $unwind: ‘$items’ },
{
$group: {
_id: ‘$items.productId’,
totalQuantity: { $sum: ‘$items.quantity’ },
totalRevenue: { $sum: { $multiply: [‘$items.price’, ‘$items.quantity’] } },
ordersCount: { $sum: 1 }
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
باز کردن چند آرایه
db.collection(‘restaurants’).aggregate([
{ $unwind: ‘$categories’ },
{ $unwind: ‘$reviews’ },
{
$group: {
_id: ‘$categories’,
averageRating: { $avg: ‘$reviews.rating’ },
reviewCount: { $sum: 1 }
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اسناد گروهی
را $group عملگر در MongoDB برای گروه بندی اسناد توسط یک کلید مشخص استفاده می شود.
db.collection(‘orders’).aggregate([
{ $group: { _id: ‘$cust_id’, total: { $sum: ‘$amount’ } } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
گروه و شمارش
db.collection(‘orders’).aggregate([
{ $group: { _id: ‘$status’, count: { $sum: 1 } } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
گروه و جمع
db.collection(‘orders’).aggregate([
{ $group: { _id: ‘$status’, total: { $sum: ‘$amount’ } } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
گروه و میانگین
db.collection(‘orders’).aggregate([
{ $group: { _id: ‘$status’, average: { $avg: ‘$amount’ } } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
گروه و فشار
db.collection(‘orders’).aggregate([
{ $group: { _id: ‘$cust_id’, items: { $push: ‘$item’ } } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
گروه با فیلدهای متعدد
db.collection(‘orders’).aggregate([
{
$group: {
_id: {
status: ‘$status’,
category: ‘$category’
},
count: { $sum: 1 },
totalAmount: { $sum: ‘$amount’ }
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
گروه با عملیات تاریخ
db.collection(‘orders’).aggregate([
{
$group: {
_id: {
year: { $year: ‘$orderDate’ },
month: { $month: ‘$orderDate’ }
},
totalOrders: { $sum: 1 },
revenue: { $sum: ‘$amount’ }
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
زمینه های پروژه
را $project عملگر در MongoDB برای گنجاندن، حذف یا تغییر نام فیلدها در اسناد خروجی استفاده می شود.
db.collection(‘orders’).aggregate([
{ $project: { _id: 0, cust_id: 1, amount: 1 } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پروژه با فیلدهای محاسبه شده
db.collection(‘orders’).aggregate([
{
$project: {
_id: 0,
cust_id: 1,
amount: 1,
discount: { $subtract: [‘$total’, ‘$amount’] }
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پروژه با عملیات آرایه
db.collection(‘orders’).aggregate([
{
$project: {
orderId: 1,
itemCount: { $size: ‘$items’ },
firstItem: { $arrayElemAt: [‘$items’, 0] },
lastItem: { $arrayElemAt: [‘$items’, -1] },
items: {
$map: {
input: ‘$items’,
as: ‘item’,
in: {
name: ‘$$item.name’,
subtotal: {
$multiply: [‘$$item.price’, ‘$$item.quantity’]
}
}
}
}
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پروژه با عملیات رشته
db.collection(‘users’).aggregate([
{
$project: {
fullName: { $concat: [‘$firstName’, ‘ ‘, ‘$lastName’] },
email: { $toLower: ‘$email’ },
age: { $toString: ‘$age’ }
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پروژه با فیلدهای شرطی
db.collection(‘users’).aggregate([
{
$project: {
name: 1,
status: {
$cond: {
if: { $gte: [‘$age’, 18] },
then: ‘Adult’,
else: ‘Minor’
}
}
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
چندین تجمع را اجرا کنید
شما می توانید چندین خط لوله تجمیع را در یک پرس و جو با استفاده از $facet اپراتور
db.collection(‘orders’).aggregate([
{
$facet: {
totalAmount: [
{ $group: { _id: null, total: { $sum: ‘$amount’ } } }
],
averageAmount: [
{ $group: { _id: null, average: { $avg: ‘$amount’ } } }
]
}
}
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
معاملات
تراکنش ها در MongoDB به شما این امکان را می دهند که چندین عملیات را به عنوان یک واحد کار، همه یا هیچ انجام دهید. آنها یکپارچگی و سازگاری داده ها را در اسناد و مجموعه های متعدد تضمین می کنند.
ویژگی های تراکنش (ACID)
استفاده از تراکنش ها
برای استفاده از تراکنش ها در MongoDB، معمولاً این مراحل را دنبال کنید:
یک جلسه را شروع کنید
یک معامله را شروع کنید
عملیات را انجام دهد
معامله را متعهد یا لغو کنید
// Define a client
const { MongoClient } = require(‘mongodb’);
const client = new MongoClient(‘mongodb://localhost:27017’);
//…
// Start a session
const session = client.startSession();
try {
session.startTransaction();
// Perform multiple operations
await collection1.updateOne({ _id: 1 }, { $set: { status: ‘processing’ } }, { session });
await collection2.insertOne({ orderId: 1, items: [‘item1’, ‘item2’] }, { session });
// Commit the transaction
await session.commitTransaction();
} catch (error) {
// If an error occurred, abort the transaction
await session.abortTransaction();
console.error(‘Transaction aborted:’, error);
} finally {
// End the session
session.endSession();
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
ملاحظات برای معاملات
عملکرد: تراکنش ها ممکن است بر عملکرد تأثیر بگذارد، به ویژه برای بارهای کاری سنگین.
تایم اوت: تراکنش ها دارای مهلت زمانی پیش فرض 60 ثانیه هستند.
مجموعه های ماکت: تراکنش ها نیاز به پیکربندی مجموعه ماکت دارند.
خوشه های خرد شده: معاملات روی خوشه های خرد شده ملاحظات و محدودیت های بیشتری دارند.
با استفاده از تراکنشها، میتوانید از یکپارچگی و یکپارچگی دادهها در چندین عملیات در MongoDB اطمینان حاصل کنید، بهویژه زمانی که با مدلهای داده پیچیده یا منطق تجاری مهم سروکار دارید.
مجموعه های ماکت
مجموعه replica گروهی از نمونه های MongoDB است که مجموعه داده های یکسانی را حفظ می کند. مجموعههای Replica افزونگی و در دسترس بودن بالا را فراهم میکنند.
اجزای یک مجموعه ماکت
اولیه: تمام عملیات نوشتن را دریافت می کند.
ثانویه: داده های اولیه را تکرار می کند. می تواند برای عملیات خواندن استفاده شود.
داور: در انتخابات مقدماتی شرکت می کند اما داده ای ندارد.
پیکربندی مجموعه کپی
برای پیکربندی یک مجموعه کپی، از rs.initiate() روش
rs.initiate({
_id: ‘rs0’,
members: [
{ _id: 0, host: ‘mongo1:27017’ },
{ _id: 1, host: ‘mongo2:27017’ },
{ _id: 2, host: ‘mongo3:27017’, arbiterOnly: true }
]
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اولویت را بخوانید
اولویت خواندن در MongoDB نحوه توزیع عملیات خواندن در مجموعه replica را تعیین می کند.
اولیه: از ابتدایی می خواند.
ثانویه: از ثانویه می خواند.
Primary Preferred: در صورت وجود از اولیه می خواند، در غیر این صورت از ثانویه.
ثانویه ترجیح داده شده است: در صورت موجود بودن از ثانویه می خواند، در غیر این صورت از اولیه می خواند.
نزدیکترین: از نزدیکترین عضو مجموعه ماکت می خواند.
db.collection(‘users’).find().readPref(‘secondary’);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نگرانی را بنویسید
نگرانی نوشتن در MongoDB تعیین کننده سطح تایید برای عملیات نوشتن است.
w: 0: بدون تایید
w: 1: قدردانی از ابتدایی.
w: اکثریت: تصدیق اکثریت مجموعه ماکت.
db.collection(‘users’).insertOne({ name: ‘Alice’ }, { writeConcern: { w: ‘major
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
Failover خودکار
MongoDB از مکانیزم ضربان قلب برای تشخیص در دسترس بودن اعضای مجموعه replica استفاده می کند. اگر دوره اولیه در دسترس نباشد، یک مقدماتی جدید انتخاب می شود.
Primary بر اساس تعداد آرای اعضای مجموعه ماکت انتخاب می شود.
Secondary در صورتی که اولیه در دسترس نباشد می توان به ابتدایی ارتقا داد.
Arbiter برای شکستن تساوی در انتخابات استفاده می شود.
Failover دستی
شما می توانید با وادار کردن یک عضو مجموعه replica برای تبدیل شدن به اصلی، یک Failover دستی را در MongoDB آغاز کنید.
rs.stepDown();
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
شاردینگ
Sharding روشی برای توزیع داده ها در چندین ماشین است. این به شما امکان می دهد با افزودن ماشین های بیشتر به سیستم خود، مقیاس افقی را انجام دهید.
یک مجموعه به تکههایی تقسیم میشود و هر تکه روی یک قطعه متفاوت ذخیره میشود.
هر Shard زیرمجموعه ای از داده ها در یک خوشه خرد شده است.
اجزای شاردینگ
شارد: زیر مجموعه ای از داده ها در یک خوشه خرد شده.
سرور پیکربندی: فوق داده ها و تنظیمات پیکربندی را برای خوشه ذخیره می کند.
کوئری روتر: کوئری ها را به قطعه مناسب هدایت می کند.
کلید شاردینگ
کلید اشتراک گذاری فیلدی است که برای توزیع داده ها در بین قطعات استفاده می شود. برای اطمینان از توزیع متعادل داده ها باید با دقت انتخاب شود.
db.collection.createIndex({ _id: ‘hashed’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هنگام انتخاب یک کلید خرد، فاکتورهای زیر را در نظر بگیرید:
کاردینالیته: تعداد مقادیر منحصر به فرد در کلید خرده.
مقیاس بندی را بنویسید: توانایی توزیع عملیات نوشتن در بین خرده ها.
جداسازی پرس و جو: توانایی هدف قرار دادن خرده های خاص برای عملیات خواندن.
استراتژی های کلید شارد
Hashed Sharding: با استفاده از یک تابع هش، داده ها را به طور یکنواخت در بین خرده ها توزیع می کند.
Range Sharding: داده ها را بر اساس محدوده ای از مقادیر در کلید خرده توزیع می کند.
شاردینگ مرکب: داده ها را بر اساس چند فیلد در کلید خرده توزیع می کند.
db.collection.createIndex({ _id: ‘hashed’ });
db.collection.createIndex({ date: 1 });
db.collection.createIndex({ country: 1, city: 1 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
مانگوس
Mongoose یک کتابخانه Object Data Modeling (ODM) برای MongoDB و Node.js است. این یک راه حل مبتنی بر طرحواره برای مدل سازی داده های برنامه شما ارائه می دهد.
اتصال به MongoDB
برای اتصال به MongoDB با استفاده از Mongoose، از connect() روش
const mongoose = require(‘mongoose’);
mongoose.connect(‘mongodb://localhost:27017/myapp’, { useNewUrlParser: true, useUnifiedTopology: true });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تعریف طرحواره
طرحواره Mongoose ساختار اسناد را در یک مجموعه تعریف می کند.
const userSchema = new mongoose.Schema({
name: String,
age: Number
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
ایجاد یک مدل
مدل Mongoose کلاسی است که مجموعه ای را در MongoDB نشان می دهد.
const User = mongoose.model(‘User’, userSchema);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
درج اسناد
برای درج یک سند در یک مجموعه، یک نمونه از مدل ایجاد می کنید و آن را فراخوانی می کنید save() روش
const user = new User({ name: ‘Alice’, age: 25 });
user.save();
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
استعلام اسناد
برای درخواست اسناد از یک مجموعه، از find() روش
User.find({ name: ‘Alice’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
با پروجکشن:
User.find({ name: ‘Alice’ }, { name: 1, age: 1 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
به روز رسانی اسناد
برای به روز رسانی اسناد در یک مجموعه، از updateOne() روش
User.updateOne({ name: ‘Alice’ }, { age: 26 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حذف اسناد
برای حذف اسناد از مجموعه، از deleteOne() روش
User.deleteOne({ name: ‘Alice’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
میان افزار
میان افزار Mongoose توابعی هستند که قبل یا بعد از عملیات خاصی اجرا می شوند.
userSchema.pre(‘save’, function(next) {
console.log(‘Saving user…’);
next();
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
مجازی
مجازیهای Mongoose ویژگیهای سندی هستند که میتوانید آنها را دریافت و تنظیم کنید، اما در MongoDB حفظ نمیشوند.
userSchema.virtual(‘fullName’).get(function() {
return this.name + ‘ ‘ + this.age;
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پلاگین ها
پلاگین های Mongoose قطعاتی از میان افزار طرحواره قابل استفاده مجدد هستند که می توانند به هر طرحی اضافه شوند.
const timestampPlugin = require(‘./plugins/timestamp’);
userSchema.plugin(timestampPlugin);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
معاملات
معاملات Mongoose به شما این امکان را می دهد که چندین عملیات را روی چندین سند در یک تراکنش انجام دهید.
const session = await mongoose.startSession();
session.startTransaction();
try {
await User.create({ name: ‘Alice’ }, { session });
await User.create({ name: ‘Bob’ }, { session });
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
} finally {
session.endSession();
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تجمیع
Mongoose یک API روان برای ساخت خطوط لوله تجمیع ارائه می دهد.
const result = await User.aggregate([
{ $match: { name: ‘Alice’ } },
{ $group: { _id: ‘$name’, total: { $sum: ‘$age’ } }
]);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
شاخص ها
Mongoose به شما این امکان را می دهد که شاخص ها را روی طرحواره های خود تعریف کنید.
userSchema.index({ name: 1 });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
جمعیت
جمعیت Mongoose به شما امکان می دهد به اسناد موجود در مجموعه های دیگر مراجعه کنید.
const userSchema = new mongoose.Schema({
name: String,
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: ‘Post’ }]
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اعتبار سنجی
Mongoose اعتبار سنجی داخلی را برای فیلدهای طرحواره فراهم می کند.
const userSchema = new mongoose.Schema({
name: { type: String, required: true }
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
الگوها
الگو
توضیحات
استفاده از مورد
مزایا
معایب
الگوی سطل
اسناد مرتبط را در “سطل ها” یا آرایه هایی با اندازه ثابت گروه بندی می کند
داده های سری زمانی، خوانش حسگر اینترنت اشیا
– تعداد اسناد را کاهش می دهد- عملکرد پرس و جو را برای اسکن محدوده بهبود می بخشد
– مجتمع برای به روز رسانی آیتم های فردی- ممکن است منجر به رشد سند شود
الگوی صفت
مجموعه ای از فیلدها را با الگوهای دسترسی مشابه به عنوان یک سند جاسازی شده ذخیره می کند
محصولات با ویژگی های متفاوت
– طرحواره انعطاف پذیر- پرس و جو کارآمد از ویژگی های رایج
– پرس و جوهای پیچیده تر برای ویژگی های خاص- پتانسیل برای زمینه های استفاده نشده
الگوی پرت
داده های رایج را در یک مجموعه و داده های کمیاب و بزرگ را در مجموعه دیگر ذخیره می کند
پست های رسانه های اجتماعی با سطوح تعامل متفاوت
– برای عملکرد کیس معمولی بهینه می شود- از مسائل مربوط به اندازه سند جلوگیری می کند
– برای موارد پرت به دو پرس و جو نیاز دارد- منطق برنامه پیچیده تر
الگوی زیر مجموعه
زیر مجموعه ای از فیلدها را از یک سند در مجموعه ای جداگانه ذخیره می کند
نمایههای کاربر با فیلدهای پربازدید
– عملکرد خواندن را برای پرس و جوهای رایج بهبود می بخشد- اندازه مجموعه کاری را کاهش می دهد
– تکرار داده ها- نیاز به همگام نگه داشتن زیر مجموعه ها دارد
پرسش و پاسخ
Exec() در Mongoose چیست؟
را exec() تابع در Mongoose برای اجرای یک پرس و جو و بازگشت یک وعده استفاده می شود. این به شما امکان می دهد تا متدهای پرس و جو را زنجیره ای کنید و سپس در پایان پرس و جو را اجرا کنید.
User.find({ name: ‘Alice’ }).exec();
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
می توانید پرس و جو را بدون آن اجرا کنید exec()، با پاسخ به تماس یا استفاده از async/wait.
User.find({ name: ‘Alice’ }, (error, users) => {
console.log(users);
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
const users = await User.find({ name: ‘Alice’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
چه فرقی با هم دارند findOne() و find() در مونگوس؟
find(): آرایه ای از تمام اسنادی را برمی گرداند که با معیارهای پرس و جو مطابقت دارند.
findOne(): اولین سندی را برمی گرداند که با معیارهای پرس و جو مطابقت دارد.
تفاوت بین Model.create() و new Model().save() در Mongoose چیست؟
Model.create(): یک سند جدید ایجاد می کند و آن را در یک مرحله در پایگاه داده ذخیره می کند.
User.create({ name: ‘Alice’ });
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
new Model().save(): نمونه جدیدی از مدل را تکرار می کند اما بلافاصله آن را در پایگاه داده ذخیره نمی کند. قبل از فراخوانی .save() می توانید نمونه را تغییر دهید، اعتبارسنجی انجام دهید یا هر عملیات دیگری را اجرا کنید تا تغییرات ادامه یابد.
const doc = new Model({ name: ‘John’, age: 30 });
doc.age = 31; // Modify the document
await doc.save(); // Save the document after modification
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هدف از متد lean() در کوئری های Mongoose چیست و چه زمانی باید از آن استفاده کرد؟
را lean() متد در جستارهای Mongoose، اشیاء جاوا اسکریپت ساده را به جای اسناد Mongoose برمی گرداند که دارای ویژگی های اضافی زیادی هستند، مانند دریافت کننده ها، تنظیم کننده ها و روش هایی که برای کار با سند مفید هستند. زمانی که به ویژگیهای کامل سند Mongoose نیاز ندارید و میخواهید عملکرد پرس و جو را بهبود ببخشید، باید از آن استفاده کنید.
User.find({ name: ‘Alice’ }).lean();
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
چگونه حذف های نرم را در Mongoose پیاده سازی کنیم؟
حذف های نرم در Mongoose شامل علامت گذاری اسناد به عنوان حذف شده به جای حذف فیزیکی آنها از پایگاه داده است. با افزودن a می توانید به این هدف برسید deleted را در طرحواره خود وارد کنید و هنگام حذف سند، آن را روی true تنظیم کنید.
const userSchema = new mongoose.Schema({
name: String,
deleted: { type: Boolean, default: false }
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
از پیش میان افزار برای حذف اسناد حذف شده از نتایج پرس و جو استفاده کنید.
userSchema.pre(/^find/, function(next) {
this.where({ deleted: false });
next();
});
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
یک روش برای “حذف نرم” یک سند اضافه کنید.
userSchema.methods.softDelete = function() {
this.deleted = true;
return this.save();
};
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
معماری MongoDB
MongoDB یک پایگاه داده محبوب NoSQL است که برای عملکرد بالا، در دسترس بودن بالا و مقیاس پذیری آسان طراحی شده است. این داده ها را در اسناد منعطف و JSON مانند ذخیره می کند و کار با داده های ساختاریافته، نیمه ساختاریافته و بدون ساختار را آسان می کند.
- پایگاه داده: ظرفی برای مجموعه ها.
- مجموعه: گروهی از اسناد MongoDB.
- سند: مجموعه ای از جفت های کلید-مقدار (شبیه به اشیاء JSON).
عملیات CRUD
CRUD مخفف Create، Read، Update و Delete است. اینها عملیات اساسی برای تعامل با داده ها در MongoDB هستند.

ایجاد کنید
برای درج یک سند جدید در مجموعه، از insertOne() یا insertMany() روش ها
insertOne
db.collection('users').insertOne({ name: 'Alice', age: 25 });
فیلد _id که به صورت خودکار ایجاد شده است
اگر یک را مشخص نکنید _id در فیلد، MongoDB به طور خودکار یک شناسه منحصر به فرد برای هر سند ایجاد می کند. برای مشخص کردن خودتون _id مقدار، می توانید آن را در سند وارد کنید.
db.collection('users').insertOne({ _id: 1, name: 'Alice', age: 25 });
اطمینان حاصل کنید که به موارد تکراری رسیدگی کنید _id ارزش ها برای جلوگیری از تعارض
درج بسیاری
برای درج چندین سند، می توانید از insertMany() روش
db.collection('users').insertMany([
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
]);
اگر یکی از اسناد درج نشود، عملیات لغو می شود مگر اینکه شما آن را مشخص کنید ordered: false گزینه
به عنوان مثال اگر شما طرحواره وادیاسیون مانند این دارید. به Schema Validation بروید
db.createCollection('users', {
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['name', 'age'],
properties: {
name: { bsonType: 'string' },
age: { bsonType: 'int' }
}
}
}
});
db.collection('users').insertMany([
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie' } // missing age field
], { ordered: false });
سند سوم نادیده گرفته می شود و دو سند اول درج می شود.
نتیجه خواهد بود
{
acknowledged: true,
insertedIds: {
'0': ObjectId("..."), // ID for Alice
'1': ObjectId("...") // ID for Bob
}
}
بخوانید
برای خواندن اسناد از یک مجموعه، از find() روش متد find() چندین سند را بازیابی می کند و مکان نما را برمی گرداند که می تواند برای دسترسی به اسناد تکرار شود.
پیدا کردن
const cursor = db.collection('users').find({ age: { $gte: 18 } });
در Node.js می توانید مکان نما را با استفاده از آرایه تبدیل کنید toArray() روش
const docs = await cursor.toArray();
console.log(docs)
نتیجه:
[
{ "_id": 1, "name": "Alice", "age": 25 },
{ "_id": 2, "name": "Bob", "age": 30 }
]
findOne
برای بازیابی یک سند، می توانید از findOne() روش
db.collection('users').findOne({ name: 'Alice' });
نتیجه:
{ "_id": 1, "name": "Alice", "age": 25 }
فرافکنی
می توانید با استفاده از پارامتر طرح ریزی مشخص کنید که کدام فیلدها در نتیجه گنجانده یا حذف شوند.
db.collection('users').find({}, { projection: { name: 1, age: 1 } });
نتیجه:
[
{ "_id": 1, "name": "Alice", "age": 25 },
{ "_id": 2, "name": "Bob", "age": 30 }
]
استفاده از طرح ریزی یک عمل حیاتی است، به خصوص هنگام کار با مجموعه های بزرگ. این مقدار داده های منتقل شده از طریق شبکه را کاهش می دهد و عملکرد پرس و جو را بهبود می بخشد. همچنین از قرار گرفتن در معرض ناخواسته داده های حساس جلوگیری می کند.
به روز رسانی
برای به روز رسانی اسناد موجود، از updateOne() یا updateMany() روش ها
db.collection('users').updateOne({ name: 'Alice' }, { $set: { age: 26 } });
db.collection('users').updateMany({ city: 'New York' }, { $set: { city: 'San Francisco' } });
هدف نتیجه
{
"acknowledged": true,
"matchedCount": 5,
"modifiedCount": 5,
"upsertedId": null,
"upsertedCount": 0
}
بر اساس شی نتیجه شما می توانید پاسخ مناسب را برگردانید.
const result = await db.collection('users').updateOne(
{ _id: userId },
{ $set: { age: 30 } }
);
if (result.matchedCount === 0) {
return { success: false, message: 'No matching document found' };
}
if (result.modifiedCount === 0) {
return { success: true, message: 'Document already up-to-date' };
}
return { success: true, message: 'Document updated successfully' };
ناراحت
اگر میخواهید سند جدیدی را در زمانی که هیچ سند منطبقی یافت نشد وارد کنید، میتوانید از آن استفاده کنید upsert گزینه
db.collection('users').updateOne(
{ name: 'Alice' },
{ $set: { age: 26 }
},
{ upsert: true }
);
هدف نتیجه
{
"acknowledged": true,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedId": ObjectId("..."),
"upsertedCount": 1
}
FindOneAndUpdate
این روش ترکیبی از findOne() و updateOne() به عنوان عملیات اتمی عملیات اتمی عملیاتی است که به صورت یک واحد کار انجام می شود. این به جلوگیری از شرایط مسابقه و اطمینان از سازگاری داده ها کمک می کند.
// Update or create a user profile
db.users.findOneAndUpdate(
{ email: "alice@example.com" },
{ $set: { age: 26 } },
{ returnDocument: 'after' }
);
هدف نتیجه
returnDocument می تواند “قبل” یا “بعد” برای بازگرداندن سند قبل یا بعد از به روز رسانی باشد.
{
"value": {
"_id": ObjectId("5f7d3b1c8e1f9a1c9c8e1f9a"),
"email": "alice@example.com",
"age": 26,
"name": "Alice"
},
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"ok": 1
}
به روز رسانی اپراتورها

// $set operator to update fields
db.collection('users').updateOne({ name: 'Alice' }, { $set: { age: 26, city: 'New York' } });
// $set operator to update nested fields
db.collection('users').updateOne({ name: 'Alice' }, { $set: { 'address.city': 'New York' } });
// $inc operator to increment a field
db.collection('users').updateOne({ name: 'Alice' }, { $inc: { age: 1 } });
// $mul operator to multiply a field value
db.collection('users').updateOne({ name: 'Alice' }, { $mul: { age: 2 } });
// $unset operator to remove a field
db.collection('users').updateOne({ name: 'Alice' }, { $unset: { city: '' } });
// $rename operator to rename a field
db.collection('users').updateOne({ name: 'Alice' }, { $rename: { city: 'location' } });
حذف کنید
برای حذف اسناد از مجموعه، از deleteOne() یا deleteMany() روش ها
db.collection('users').deleteOne({ name: 'Alice' }); // delete first matching document
db.collection('users').deleteMany({ city: 'New York' });
هدف نتیجه
{
"acknowledged": true,
"deletedCount": 1
}
بر اساس شی نتیجه شما می توانید پاسخ مناسب را برگردانید.
const result = await db.collection('users').deleteOne({ _id: userId });
if (result.deletedCount === 0) {
return { success: false, message: 'No matching document found' };
}
return { success: true, message: 'Document deleted successfully' };
از اطلاعات خود نسخه پشتیبان تهیه کنید: قبل از حذف اسناد، مطمئن شوید که از اطلاعات خود نسخه پشتیبان تهیه کرده اید تا از از دست رفتن تصادفی داده ها جلوگیری شود.
حذف نرم: به جای حذف دائمی اسناد، می توانید با افزودن a آنها را به عنوان حذف شده علامت گذاری کنید deletedAt زمینه این به شما امکان می دهد داده ها را برای اهداف ممیزی یا بازیابی نگهداری کنید.
db.collection('users').updateOne(
{ _id: userId },
{ $set: { deletedAt: new Date() } }
);
فیلتر دقیق: هنگام حذف اسناد، از یک فیلتر دقیق استفاده کنید تا از حذف ناخواسته اسناد بیشتر از آنچه در نظر گرفته شده است جلوگیری کنید. می توانید از find() برای پیش نمایش اسنادی که قبل از اجرای عملیات حذف حذف می شوند استفاده کنید.
اعتبار سنجی طرحواره
مدل در مقابل الگوی DAO
نمونه همه مدل
الگوی مدل
- داده ها و رفتار را در بر می گیرد. این ممکن است شامل اعتبار سنجی، منطق تجاری و عملیات پایگاه داده باشد. این می تواند به “چاق” اما اجرای ساده تر تبدیل شود.
این نمونه ای از ساختار فایل ها برای الگوی مدل است
src/
├── config/
│ └── database.js
│
├── models/
│ └── todo.model.js
│
├── controllers/
│ └── todo.controller.js
│
├── routes/
│ └── todo.routes.js
│
├── middleware/
│ └── auth.middleware.js
│
└── app.js
اگر مدل خیلی پیچیده شد، می توانید آن را به چندین فایل یا کلاس تقسیم کنید. به عنوان مثال، می توانید فایل های جداگانه ای برای اعتبار سنجی، منطق تجاری و عملیات پایگاه داده داشته باشید.
پیکربندی داده ها
// src/config/database.js
const { MongoClient } = require('mongodb');
const dbConfig = {
url: process.env.MONGODB_URI || 'mongodb://localhost:27017',
dbName: 'todoapp'
};
let db = null;
const connectDB = async () => {
try {
const client = await MongoClient.connect(dbConfig.url, {
useUnifiedTopology: true
});
db = client.db(dbConfig.dbName);
console.log('Connected to MongoDB successfully');
return db;
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
};
const getDB = () => {
if (!db) {
throw new Error('Database not initialized');
}
return db;
};
module.exports = { connectDB, getDB };
مدل
فایل مدل شامل ساختار داده، اعتبارسنجی و عملیات پایگاه داده برای یک آیتم انجام می شود. این شامل داده ها و رفتار مربوط به کارها است.
// src/models/todo.model.js
onst { ObjectId } = require('mongodb');
const { getDB } = require('../config/database');
const COLLECTION_NAME = 'todos';
// Validation functions
const validateTodoData = (todoData) => {
const errors = [];
if (!todoData.title) {
errors.push('Title is required');
} else if (todoData.title.length < 3) {
errors.push('Title must be at least 3 characters long');
}
if (todoData.status && !['pending', 'in-progress', 'completed'].includes(todoData.status)) {
errors.push('Invalid status. Must be pending, in-progress, or completed');
}
if (todoData.dueDate && new Date(todoData.dueDate) < new Date()) {
errors.push('Due date cannot be in the past');
}
if (todoData.priority && !['low', 'medium', 'high'].includes(todoData.priority)) {
errors.push('Invalid priority. Must be low, medium, or high');
}
return errors;
};
const todoModel = {
async create(todoData) {
const errors = validateTodoData(todoData);
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
const db = getDB();
const todo = {
...todoData,
title: todoData.title.trim(),
status: todoData.status || 'pending',
priority: todoData.priority || 'medium',
createdAt: new Date(),
updatedAt: new Date(),
completedAt: null
};
const result = await db.collection(COLLECTION_NAME).insertOne(todo);
return { ...todo, _id: result.insertedId };
},
async findById(id) {
if (!ObjectId.isValid(id)) {
throw new Error('Invalid todo ID');
}
const db = getDB();
const todo = await db.collection(COLLECTION_NAME).findOne({
_id: new ObjectId(id)
});
if (!todo) {
throw new Error('Todo not found');
}
return todo;
},
async find(query = {}, options = {}) {
const db = getDB();
const {
page = 1,
limit = 10,
sortBy = 'createdAt',
sortOrder = -1
} = options;
if (page < 1 || limit < 1) {
throw new Error('Invalid pagination parameters');
}
const skip = (page - 1) * limit;
const sortOptions = { [sortBy]: sortOrder };
// Apply filters
const filters = { ...query };
if (filters.priority) {
if (!['low', 'medium', 'high'].includes(filters.priority)) {
throw new Error('Invalid priority filter');
}
}
if (filters.status) {
if (!['pending', 'in-progress', 'completed'].includes(filters.status)) {
throw new Error('Invalid status filter');
}
}
const [todos, totalCount] = await Promise.all([
db.collection(COLLECTION_NAME)
.find(filters)
.sort(sortOptions)
.skip(skip)
.limit(limit)
.toArray(),
db.collection(COLLECTION_NAME)
.countDocuments(filters)
]);
return {
todos,
pagination: {
total: totalCount,
page,
limit,
pages: Math.ceil(totalCount / limit)
}
};
},
async update(id, updateData) {
if (!ObjectId.isValid(id)) {
throw new Error('Invalid todo ID');
}
const errors = validateTodoData(updateData);
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
const db = getDB();
const existingTodo = await this.findById(id);
// Business logic for status changes
if (updateData.status === 'completed' && existingTodo.status !== 'completed') {
updateData.completedAt = new Date();
}
if (updateData.status && updateData.status !== 'completed') {
updateData.completedAt = null;
}
const result = await db.collection(COLLECTION_NAME).findOneAndUpdate(
{ _id: new ObjectId(id) },
{
$set: {
...updateData,
updatedAt: new Date()
}
},
{ returnDocument: 'after' }
);
if (!result.value) {
throw new Error('Todo not found');
}
return result.value;
},
async delete(id) {
if (!ObjectId.isValid(id)) {
throw new Error('Invalid todo ID');
}
const db = getDB();
const result = await db.collection(COLLECTION_NAME).deleteOne({
_id: new ObjectId(id)
});
if (result.deletedCount === 0) {
throw new Error('Todo not found');
}
return true;
},
// Additional business logic methods
async markAsComplete(id) {
return await this.update(id, {
status: 'completed'
});
},
async findOverdue() {
const db = getDB();
return await db.collection(COLLECTION_NAME).find({
dueDate: { $lt: new Date() },
status: { $ne: 'completed' }
}).toArray();
}
};
نمونه همه DAO
الگوی DAO
- منطق دسترسی به داده ها از منطق تجاری جدا شده است.
- لایه سرویس برای منطق تجاری و لایه DAO برای دسترسی به داده ها.
- برای آزمایش با تمسخر لایه دسترسی به داده بهتر است.
- تزریق وابستگی را بهتر پشتیبانی می کند.
- پیاده سازی پیچیده تر و تکرار کد.

src/
├── config/
│ └── database.js # Database connection configuration
│
├── models/
│ └── todo.entity.js # Data structure/schema definition
│
├── daos/
│ └── todo.dao.js # Data Access Object - handles database operations
│
├── services/
│ └── todo.service.js # Business logic layer
│
├── controllers/
│ └── todo.controller.js # Request handling & response formatting
│
├── routes/
│ └── todo.routes.js # Route definitions
│
└── app.js # Application entry point
پیکربندی پایگاه داده
// src/config/database.js
const { MongoClient } = require('mongodb');
class Database {
constructor(config) {
this.config = {
url: config.url || process.env.MONGODB_URI || 'mongodb://localhost:27017',
dbName: config.dbName || process.env.DB_NAME || 'todoapp',
options: {
useUnifiedTopology: true,
...config.options
}
};
this.client = null;
this.db = null;
}
async connect() {
try {
this.client = await MongoClient.connect(this.config.url, this.config.options);
this.db = this.client.db(this.config.dbName);
console.log('Connected to MongoDB successfully');
return this.db;
} catch (error) {
console.error('MongoDB connection error:', error);
throw new DatabaseError('Failed to connect to database', error);
}
}
async disconnect() {
try {
if (this.client) {
await this.client.close();
this.client = null;
this.db = null;
console.log('Disconnected from MongoDB');
}
} catch (error) {
console.error('MongoDB disconnection error:', error);
throw new DatabaseError('Failed to disconnect from database', error);
}
}
getDB() {
if (!this.db) {
throw new DatabaseError('Database not initialized. Call connect() first.');
}
return this.db;
}
}
موجودیت
// src/models/todo.entity.js
class Todo {
static STATUS = {
PENDING: 'pending',
IN_PROGRESS: 'in-progress',
COMPLETED: 'completed'
};
static PRIORITY = {
LOW: 'low',
MEDIUM: 'medium',
HIGH: 'high'
};
constructor(data = {}) {
this._id = data._id || null;
this.title = data.title || '';
this.description = data.description || '';
this.status = data.status || Todo.STATUS.PENDING;
this.priority = data.priority || Todo.PRIORITY.MEDIUM;
this.dueDate = data.dueDate ? new Date(data.dueDate) : null;
this.createdAt = data.createdAt ? new Date(data.createdAt) : new Date();
this.updatedAt = data.updatedAt ? new Date(data.updatedAt) : new Date();
this.completedAt = data.completedAt ? new Date(data.completedAt) : null;
this.tags = Array.isArray(data.tags) ? [...data.tags] : [];
this.assignedTo = data.assignedTo || null;
}
validate() {
const errors = [];
if (!this.title?.trim()) {
errors.push('Title is required');
} else if (this.title.trim().length < 3) {
errors.push('Title must be at least 3 characters long');
}
if (this.status && !Object.values(Todo.STATUS).includes(this.status)) {
errors.push(`Invalid status. Must be one of: ${Object.values(Todo.STATUS).join(', ')}`);
}
if (this.dueDate) {
if (!(this.dueDate instanceof Date) || isNaN(this.dueDate.getTime())) {
errors.push('Invalid due date format');
} else if (this.dueDate < new Date()) {
errors.push('Due date cannot be in the past');
}
}
if (this.priority && !Object.values(Todo.PRIORITY).includes(this.priority)) {
errors.push(`Invalid priority. Must be one of: ${Object.values(Todo.PRIORITY).join(', ')}`);
}
if (this.tags && !Array.isArray(this.tags)) {
errors.push('Tags must be an array');
}
return errors;
}
isOverdue() {
return this.dueDate && this.dueDate < new Date() && this.status !== Todo.STATUS.COMPLETED;
}
toJSON() {
return {
_id: this._id,
title: this.title,
description: this.description,
status: this.status,
priority: this.priority,
dueDate: this.dueDate,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
completedAt: this.completedAt,
tags: this.tags,
assignedTo: this.assignedTo
};
}
}
کلاس خطاهای اعتبارسنجی
// src/errors/index.js
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
this.status = 400;
}
}
class DatabaseError extends Error {
constructor(message, originalError = null) {
super(message);
this.name = 'DatabaseError';
this.status = 500;
this.originalError = originalError;
}
}
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = 'NotFoundError';
this.status = 404;
}
}
DAO
مبتنی بر کلاس با تزریق سازنده
این سبک یک کلاس BaseDAO را تعریف می کند که یک رابط تمیز و قابل استفاده مجدد با متدهایی مانند findOne، insertOne و updateOne ارائه می کند. هر DAO (مانند TodoDAO) BaseDAO را گسترش می دهد و در یک نام مجموعه خاص عبور می کند
جوانب مثبت:
- تمیز و قابل استفاده مجدد: BaseDAO یک پایه خوب است، به خصوص اگر چندین DAO دارید که از ساختارهای مشابه پیروی می کنند.
- اعتبار سنجی سازنده: اطمینان حاصل میکند که db و collectionName ارائه شدهاند، که کمک میکند مشکلات را زودتر شناسایی کنید.
- خوانایی و قابلیت نگهداری: عملیات CRUD به خوبی کپسوله شده است و می تواند به راحتی در DAO های مختلف با گسترش BaseDAO دوباره استفاده شود.
معایب:
- کد دیگ بخار بیشتر
- هر DAO باید BaseDAO را گسترش دهد، که ممکن است انعطاف پذیری را برای مواردی که یک DAO ممکن است نیاز به مقداردهی اولیه اضافی یا یک ساختار منحصر به فرد داشته باشد، محدود کند.
// src/daos/base.dao.js
class BaseDAO {
constructor(db, collectionName) {
if (!db) {
throw new Error('Database connection is required');
}
if (!collectionName) {
throw new Error('Collection name is required');
}
this.db = db;
this.collection = this.db.collection(collectionName);
}
async findOne(filter) {
try {
return await this.collection.findOne(filter);
} catch (error) {
throw new DatabaseError('Database query failed', error);
}
}
async find(filter = {}, options = {}) {
try {
return await this.collection.find(filter, options).toArray();
} catch (error) {
throw new DatabaseError('Database query failed', error);
}
}
async insertOne(data) {
try {
return await this.collection.insertOne(data);
} catch (error) {
throw new DatabaseError('Database insert failed', error);
}
}
async updateOne(filter, update, options = {}) {
try {
return await this.collection.updateOne(filter, update, options);
} catch (error) {
throw new DatabaseError('Database update failed', error);
}
}
async deleteOne(filter) {
try {
return await this.collection.deleteOne(filter);
} catch (error) {
throw new DatabaseError('Database delete failed', error);
}
}
}
// src/daos/todo.dao.js
class TodoDAO extends BaseDAO {
constructor(db) {
super(db, 'todos');
}
async create(todoData) {
const todo = new Todo(todoData);
const errors = todo.validate();
if (errors.length > 0) {
throw new ValidationError(errors.join(', '));
}
const todoToInsert = {
...todo.toJSON(),
title: todo.title.trim(),
createdAt: new Date(),
updatedAt: new Date()
};
try {
const result = await this.insertOne(todoToInsert);
return new Todo({ ...todoToInsert, _id: result.insertedId });
} catch (error) {
throw new DatabaseError('Failed to create todo', error);
}
}
async findById(id) {
if (!ObjectId.isValid(id)) {
throw new ValidationError('Invalid todo ID');
}
const todo = await this.findOne({ _id: new ObjectId(id) });
if (!todo) {
throw new NotFoundError('Todo not found');
}
return new Todo(todo);
}
async find(query = {}, options = {}) {
const {
page = 1,
limit = 10,
sortBy = 'createdAt',
sortOrder = -1,
status,
priority,
searchTerm,
fromDate,
toDate,
tags
} = options;
if (page < 1 || limit < 1) {
throw new ValidationError('Invalid pagination parameters');
}
const filter = this._buildFilter({
...query,
status,
priority,
searchTerm,
fromDate,
toDate,
tags
});
const skip = (page - 1) * limit;
const sortOptions = { [sortBy]: sortOrder };
try {
const [todos, totalCount] = await Promise.all([
this.collection
.find(filter)
.sort(sortOptions)
.skip(skip)
.limit(limit)
.toArray(),
this.collection.countDocuments(filter)
]);
return {
todos: todos.map(todo => new Todo(todo)),
pagination: {
total: totalCount,
page,
limit,
pages: Math.ceil(totalCount / limit)
}
};
} catch (error) {
throw new DatabaseError('Failed to fetch todos', error);
}
}
async update(id, updateData) {
const existingTodo = await this.findById(id);
const updatedTodo = new Todo({
...existingTodo,
...updateData,
_id: existingTodo._id,
updatedAt: new Date()
});
const errors = updatedTodo.validate();
if (errors.length > 0) {
throw new ValidationError(errors.join(', '));
}
const result = await this.updateOne(
{ _id: new ObjectId(id) },
{ $set: updatedTodo.toJSON() },
{ returnDocument: 'after' }
);
if (result.matchedCount === 0) {
throw new NotFoundError('Todo not found');
}
return updatedTodo;
}
async delete(id) {
if (!ObjectId.isValid(id)) {
throw new ValidationError('Invalid todo ID');
}
const result = await this.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 0) {
throw new NotFoundError('Todo not found');
}
return true;
}
_buildFilter(options) {
const filter = {};
if (options.status) {
if (!Object.values(Todo.STATUS).includes(options.status)) {
throw new ValidationError('Invalid status filter');
}
filter.status = options.status;
}
if (options.priority) {
if (!Object.values(Todo.PRIORITY).includes(options.priority)) {
throw new ValidationError('Invalid priority filter');
}
filter.priority = options.priority;
}
if (options.searchTerm) {
filter.$or = [
{ title: { $regex: options.searchTerm, $options: 'i' } },
{ description: { $regex: options.searchTerm, $options: 'i' } }
];
}
if (options.fromDate || options.toDate) {
filter.dueDate = {};
if (options.fromDate) {
filter.dueDate.$gte = new Date(options.fromDate);
}
if (options.toDate) {
filter.dueDate.$lte = new Date(options.toDate);
}
}
if (options.tags && Array.isArray(options.tags)) {
filter.tags = { $all: options.tags };
}
return filter;
}
}
روش استاتیک با اتصال تزریقی
استفاده ساده شده: روشهای استاتیک نیاز به نمونهسازی کلاس را برطرف میکند و سربار حافظه را کاهش میدهد.
چالش های تست: روشهای استاتیک با تزریق وابستگی انعطافپذیری کمتری دارند و باعث میشوند که تمسخرها و تستها پیچیدهتر شوند.
قابلیت استفاده مجدد محدود: روشهای استاتیک از وراثت پشتیبانی نمیکنند، بنابراین متمرکز کردن منطق مشترک بین DAOها دشوارتر است.
import { ObjectId } from 'mongodb';
let todos;
export default class TodoDAO {
static async injectDB(conn) {
if (todos) return;
try {
todos = await conn.db(process.env.DB_NAME).collection('todos');
} catch (error) {
console.error(`Unable to establish collection handles in TodoDAO: ${error}`);
}
}
static async create(todoData) {
// Initialize and validate the Todo, build the data object, and insert into the collection
}
static async findById(id) {
// Validate ID, query by `_id`, throw `NotFoundError` if not found
}
static async find(query = {}, options = {}) {
// Construct filter, pagination, and sort options, execute the find query with pagination
}
static async update(id, updateData) {
// Fetch the todo, validate and update with `updateOne`
}
static async delete(id) {
// Validate ID, delete by `_id`, and handle `NotFoundError`
}
static _buildFilter(options) {
// Generate the filter object for queries based on status, priority, search terms, etc.
}
}
خدمات
// src/services/todo.service.js
class TodoService {
constructor(todoDAO) {
if (!todoDAO) {
throw new Error('TodoDAO is required');
}
this.todoDAO = todoDAO;
}
async createTodo(todoData) {
return await this.todoDAO.create(todoData);
}
async getTodoById(id) {
return await this.todoDAO.findById(id);
}
async updateTodo(id, updateData) {
return await this.todoDAO.update(id, updateData);
}
async deleteTodo(id) {
return await this.todoDAO.delete(id);
}
async updateTodoStatus(id, status) {
const todo = await this.todoDAO.findById(id);
const updates = {
status,
updatedAt: new Date()
};
if (status === Todo.STATUS.COMPLETED && todo.status !== Todo.STATUS.COMPLETED) {
updates.completedAt = new Date();
} else if (status !== Todo.STATUS.COMPLETED && todo.status === Todo.STATUS.COMPLETED) {
updates.completedAt = null;
}
return await this.todoDAO.update(id, updates);
}
async findTodos(options = {}) {
return await this.todoDAO.find({}, options);
}
async findOverdueTodos() {
const now = new Date();
return await this.todoDAO.find({
dueDate: { $lt: now },
status: { $ne: Todo.STATUS.COMPLETED }
});
}
async findTodosByPriority(priority) {
return await this.todoDAO.find({ priority });
}
async assignTodo(id, userId) {
return await this.todoDAO.update(id, {
assignedTo: userId,
updatedAt: new Date()
});
}
async addTags(id, tags) {
const todo = await this.todoDAO.findById(id);
const uniqueTags = [...new Set([...todo.tags, ...tags])];
return await this.todoDAO.update(id, { tags: uniqueTags });
}
async removeTags(id, tags) {
const todo = await this.todoDAO.findById(id);
const updatedTags = todo.tags.filter(tag => !tags.includes(tag));
return await this.todoDAO.update(id, { tags: updatedTags });
}
}
نمایه سازی
نمایه سازی در MongoDB عملکرد پرس و جو را با ایجاد ساختارهای داده کارآمد برای بازیابی سریعتر بهبود می بخشد.

- شاخص تک فیلد: نمایه در یک فیلد واحد از یک سند.
- شاخص مرکب: فهرست در چندین فیلد.
- شاخص چند کلیدی: نمایه در فیلدهای آرایه.
- فهرست متن: از عبارت های جستجوی متنی در محتوای رشته پشتیبانی می کند.
شاخص تک فیلد
برای ایجاد ایندکس، از createIndex() روش
db.collection('users').createIndex({ name: 1 });
شاخص مرکب
یک شاخص ترکیبی در MongoDB یک نمایه در چندین فیلد در یک سند است.

db.collection('users').createIndex({ name: 1, age: 1, city: 1, country: 1, hobbies: 1 });
پیشوندهای یک نمایه مرکب را می توان برای برآوردن پرس و جوهایی استفاده کرد که با فیلدهای فهرست از چپ به راست مطابقت دارند.
db.collection('users').find({ name: 'Alice', age: 25 });
db.collection('users').find({ name: 'Alice', age: 25, city: 'New York' });
شاخص چند کلیدی
یک شاخص چند کلیدی در MongoDB یک شاخص در یک فیلد آرایه است.

db.collection('users').createIndex({ hobbies: 1 });
محدودیت های شاخص های چند کلیدی
- اگر بیش از یک فیلد یک آرایه باشد، نمی توانید یک شاخص ترکیبی ایجاد کنید.
db.collection('users').createIndex({ "hobbies": 1, "tags": 1 }); // Not allowed if both hobbies and tags are arrays
فهرست متن
فهرست های متنی در MongoDB راهی برای انجام پرس و جوهای جستجوی متنی در محتوای رشته ارائه می دهند. هر مجموعه می تواند حداکثر یک فهرست متنی داشته باشد.
db.products.createIndex({
name: "text",
description: "text",
tags: "text"
});
// Sample Data
db.products.insertMany([
{
name: "MacBook Pro 16-inch",
description: "Powerful laptop for developers",
tags: ["apple", "laptop", "computer"]
},
{
name: "iPhone 15 Pro",
description: "Latest smartphone with great camera",
tags: ["apple", "smartphone", "mobile"]
},
{
name: "Gaming Laptop ROG",
description: "High-performance gaming laptop",
tags: ["asus", "laptop", "gaming"]
}
]);
تطابق دقیق کلمات
db.products.find({ $text: { $search: "laptop" }}); // Will find MacBook and ROG
چند کلمه (OR)
// By default, multiple words are treated as logical OR
// Will find documents containing "laptop" or "gaming"
db.products.find({ $text: { $search: "laptop gaming" }});
چند کلمه (منطقی و)
// Using the plus sign to ensure both terms are present
// Will find documents containing both "laptop" and "gaming"
db.products.find({ $text: { $search: "+laptop +gaming" }});
عبارات دقیق
// Using double quotes to search for an exact phrase
// Will find documents containing the exact phrase "gaming laptop"
db.products.find({ $text: { $search: "\"gaming laptop\"" }});
حذف کلمات
db.products.find({ $text: { $search: “laptop -gaming” }}); // لپ تاپ اما نه گیمینگ
مسابقات بدون حروف بزرگ
// Text search is case-insensitive by default
// Will find all documents containing "laptop" regardless of case
db.products.find({ $text: { $search: "LAPTOP" }});
دیاکریتیک غیر حساس
// Text search ignores diacritic marks by default
// Will match "cafe" and "café"
db.products.find({ $text: { $search: "café" }});
مطابقت جزئی کلمه
// Searching for "lap" won't find "laptop"
db.products.find({ $text: { $search: "lap" }});
جستجوهای حروف عامیانه
// Wildcards are not supported in text search
db.products.find({ $text: { $search: "lap*" }});
تطبیق فازی (اشتباهات تایپی)
// Regular expressions cannot be used inside $text search
db.products.find({ $text: { $search: "/lap.*/" }});
عبارات بولی پیچیده
// Nested boolean logic is not supported in $text search
db.products.find({ $text: { $search: "(laptop AND gaming) OR (smartphone AND camera)" }});
جستجوهای حساس به حروف کوچک و بزرگ
// Cannot perform case-sensitive searches using $text
db.products.find({ $text: { $search: "MacBook", caseSensitive: true }});
جستجوی اطلس
Atlas Search یک سرویس جستجوی کاملاً مدیریت شده است که به شما امکان می دهد قابلیت های جستجوی سریع، مرتبط و متن کامل را در بالای داده های MongoDB خود ایجاد کنید. در مقایسه با فهرستهای متنی، جستجوی اطلس ویژگیهای پیشرفتهتری مانند جستجوی وجهی، تطبیق فازی و امتیازدهی مرتبط را فراهم میکند.
// Basic Atlas Search Setup
db.products.createSearchIndex({
"mappings": {
"dynamic": true,
"fields": {
"name": {
"type": "string",
"analyzer": "lucene.standard"
},
"description": {
"type": "string",
"analyzer": "lucene.english"
}
}
}
});
تطبیق فازی – غلط املایی و غلط املایی را کنترل می کند
db.products.aggregate([
{
$search: {
text: {
query: "labtop", // Will match "laptop"
path: "name",
fuzzy: { maxEdits: 1 }
}
}
}
]);
تکمیل خودکار – پیشنهادات در زمان واقعی
db.products.aggregate([
{
$search: {
autocomplete: {
query: "mac", // Will suggest "macbook", "machine", etc.
path: "name"
}
}
}
]);
امتیازدهی سفارشی – ارتباط را افزایش دهید
می تواند ارتباط برخی اسناد را بر اساس معیارهای خاص افزایش دهد.
db.products.aggregate([
{
$search: {
compound: {
must: [
{ text: { query: "laptop", path: "name" } }
],
should: [
{
text: {
query: "gaming",
path: "category",
score: { boost: { value: 2.0 } } // Boost gaming laptops
}
}
]
}
}
}
]);
- اسناد باید حاوی “لپ تاپ” در قسمت نام باشند
- اسناد حاوی “بازی” در قسمت توضیحات تقویت می شوند.
پشتیبانی چند زبانه
db.products.aggregate([
{
$search: {
text: {
query: "ordinateur portable", // French for "laptop"
path: "description",
analyzer: "lucene.french"
}
}
}
]);
جستجوی وجهی – نتایج را فیلتر کنید
جستجوی وجهی به کاربران اجازه می دهد تا نتایج جستجو را بر اساس دسته بندی ها، محدوده قیمت و سایر ویژگی ها فیلتر کنند.
db.products.aggregate([
{
$searchMeta: {
facet: {
operator: { text: { query: "laptop", path: "description" } },
facets: {
categories: { type: "string", path: "category" },
priceRanges: {
type: "number",
path: "price",
boundaries: [500, 1000, 2000]
}
}
}
}
}
]);
**Facets:**
- categoryFacet: Groups results by the category field.
- priceFacet: Groups results into price ranges defined by the boundaries.
مبادلات
علاوه بر مزایا، جستجوی اطلس در مقایسه با نمایههای متنی سنتی دارای برخی معاوضهها نیز است.
- به خوشه های M10+ نیاز دارد
- هزینه اضافی
- راه اندازی پیچیده تر
- استفاده بیشتر از منابع
عملیات آرایه
MongoDB از انواع عملیات آرایه برای کار با آرایه ها در اسناد پشتیبانی می کند.

افزودن عناصر به آرایه
برای افزودن عناصر به آرایه در یک سند، از $push اپراتور
db.collection('users').updateOne({ name: 'Alice' }, { $push: { hobbies: 'Reading' } });
پرس و جو آرایه ها با $elemMatch
با توجه به مجموعه ای از دانش آموزان با نمرات در موضوعات مختلف:
{
"_id": ObjectId("64d39a7a8b0e8c284a2c1234"),
"name": "Alice",
"scores": [
{ "subject": "Math", "score": 95 },
{ "subject": "English", "score": 88 }
]
},
{
"_id": ObjectId("64d39a808b0e8c284a2c1235"),
"name": "Bob",
"scores": [
{ "subject": "Math", "score": 78 },
{ "subject": "English", "score": 92 }
]
}
پرس و جو با $elemMatch:
برای یافتن دانشآموزانی که به طور خاص در ریاضی نمره 90 کسب کردهاند، نیاز داریم $elemMatch:
db.students.find({
scores: {
$elemMatch: { subject: "Math", score: { $gt: 90 } }
}
})
نتیجه: این فقط آلیس را برمی گرداند، زیرا او تنها دانش آموز با نمره بالای 90 در درس “ریاضی” است. $elemMatch آن را تضمین می کند همه شرایط درون عنصر آرایه باید برآورده شود.
بدون $elemMatch – این امر هم آلیس و هم باب را برمی گرداند، زیرا هر دو در دروس مختلف و نه لزوماً در درس “ریاضی” نمرات بالای 90 دارند.

اضافه کردن منحصر به فرد – $addToSet
را $addToSet عملگر در MongoDB برای افزودن عناصر به آرایه تنها در صورتی استفاده می شود که از قبل وجود نداشته باشند. این از ورودهای تکراری در آرایه جلوگیری می کند.
db.collection('users').updateOne({ name: 'Alice' }, { $addToSet: { hobbies: 'Reading' } });
چندگانه اضافه کنید $push و $each
را $push عملگر در MongoDB برای افزودن عناصر به آرایه استفاده می شود. را $each modifier به شما اجازه می دهد چندین عنصر را به آرایه اضافه کنید.
db.collection('users').updateOne({ name: 'Alice' }, { $push: { hobbies: { $each: ['Reading', 'Swimming'] } } });
افزودن مرتب شده – $push و $sort
را $push عملگر در MongoDB برای افزودن عناصر به آرایه استفاده می شود. را $sort modifier به شما اجازه می دهد تا عناصر آرایه را مرتب کنید.
db.collection('users').updateOne({ name: 'Alice' }, { $push: { scores: { $each: [85, 90], $sort: -1 } } });
فشار و مرتب سازی آرایه از اشیاء بر اساس یک فیلد خاص
db.collection('users').updateOne(
{ name: 'Alice' },
{
$push: {
scores: {
$each: [
{ score: 85, date: "2023-03-01" },
{ score: 90, date: "2023-04-01" }
],
$sort: { date: -1 }
}
}
}
);
افزودن محدود – $push و $slice
را $push عملگر در MongoDB برای افزودن عناصر به آرایه استفاده می شود. را $slice اصلاح کننده به شما امکان می دهد تعداد عناصر موجود در آرایه را محدود کنید.
db.collection('users').updateOne({ name: 'Alice' }, { $push: { scores: { $each: [85, 90], $slice: -3 } } });
اگر آلیس در حال حاضر یک آرایه نمرات مانند [70, 75, 80]، این کوئری 85 و 90 را فشار می دهد و آن را ایجاد می کند [70, 75, 80, 85, 90]. سپس $slice: -3 آن را به 3 عنصر آخر برش میدهد و در نتیجه [80, 85, 90].
حذف عناصر از یک آرایه
برای حذف عناصر از آرایه در یک سند، از $pull اپراتور
db.collection('users').updateOne({ name: 'Alice' }, { $pull: { hobbies: 'Reading' } });
اولین یا آخرین عنصر را حذف کنید – $pop
را $pop عملگر در MongoDB برای حذف اولین یا آخرین عنصر از یک آرایه استفاده می شود.
db.collection('users').updateOne({ name: 'Alice' }, { $pop: { hobbies: 1 } });
حذف چندگانه – $pullAll
را $pullAll عملگر در MongoDB برای حذف تمام رخدادهای مقادیر مشخص شده از یک آرایه استفاده می شود.
db.collection('users').updateOne({ name: 'Alice' }, { $pullAll: { hobbies: ['Reading', 'Swimming'] } });
حذف چندگانه – $pull و $in
را $pull عملگر در MongoDB برای حذف عناصر از یک آرایه استفاده می شود. را $in modifier به شما امکان می دهد چندین مقدار را برای حذف مشخص کنید.
db.collection('users').updateOne({ name: 'Alice' }, { $pull: { hobbies: { $in: ['Reading', 'Swimming'] } } });
حذف شرط – $pull و $gt
را $pull عملگر در MongoDB برای حذف عناصر از یک آرایه استفاده می شود. را $gt modifier به شما اجازه می دهد تا یک شرط برای حذف عناصر مشخص کنید.
db.collection('users').updateOne({ name: 'Alice' }, { $pull: { scores: { $gt: 85 } } });
حذف نه برابر – $pull و $ne
را $pull عملگر در MongoDB برای حذف عناصر از یک آرایه استفاده می شود. را $ne modifier به شما اجازه می دهد تا یک شرط برای حذف عناصری که برابر با یک مقدار نیستند، تعیین کنید.
db.collection('users').updateOne({ name: 'Alice' }, { $pull: { scores: { $ne: 85 } } });
شرایط به روز رسانی – $set و $
را $set عملگر در MongoDB برای به روز رسانی فیلدهای یک سند استفاده می شود. را $ عملگر موقعیتی به شما امکان می دهد اولین عنصری را که با یک شرط در یک آرایه مطابقت دارد به روز کنید.
db.collection('users').updateOne({ name: 'Alice', 'scores.subject': 'Math' }, { $set: { 'scores.$.score': 90 } });
به روز رسانی همه – $[]
را $[] عملگر در MongoDB برای به روز رسانی تمام عناصر یک آرایه که با یک شرط مطابقت دارند استفاده می شود.
db.collection('users').updateOne({ name: 'Alice' }, { $set: { 'scores.$[].score': 90 } });
برای افزایش یک فیلد در تمام عناصر یک آرایه، می توانید از $[] اپراتور با $inc اپراتور
db.collection('users').updateOne({ name: 'Alice' }, { $inc: { 'scores.$[].score': 5 } });
به روز رسانی فیلتر شده – $[
را $[ عملگر در MongoDB برای به روز رسانی عناصر در یک آرایه که با یک شرط مطابقت دارند استفاده می شود.
db.collection('users').updateOne({ name: 'Alice' }, { $set: { 'scores.$[elem].score': 90 } }, { arrayFilters: [{ 'elem.subject': 'Math' }] });
تجمیع
عملیات تجمیع، سوابق داده ها را پردازش کرده و نتایج محاسبه شده را برمی گرداند. Aggregation به شما امکان می دهد پردازش و تبدیل داده های پیچیده را انجام دهید.
خط لوله تجمع
چارچوب تجمیع در MongoDB از یک رویکرد خط لوله استفاده می کند، که در آن چندین مرحله اسناد را تغییر می دهد.
db.collection('orders').aggregate([
{ $match: { status: 'A' } },
{ $group: { _id: '$cust_id', total: { $sum: '$amount' } } },
{ $sort: { total: -1 } }
]);

به مجموعه ها بپیوندید
MongoDB از اتصالات مانند پایگاه داده های رابطه ای پشتیبانی نمی کند. در عوض، شما می توانید استفاده کنید $lookup عملگر برای انجام یک اتصال بیرونی سمت چپ بین دو مجموعه.
db.collection('orders').aggregate([
{
$lookup: {
from: 'customers',
localField: 'cust_id',
foreignField: '_id',
as: 'customer'
}
}
]);

باز کردن آرایه ها
را $unwind عملگر در MongoDB برای تجزیه یک فیلد آرایه به چندین سند استفاده می شود.
db.collection('orders').aggregate([
{ $unwind: '$items' }
]);
باز کردن و گروه
db.collection('orders').aggregate([
{ $unwind: '$items' },
{
$group: {
_id: '$items.productId',
totalQuantity: { $sum: '$items.quantity' },
totalRevenue: { $sum: { $multiply: ['$items.price', '$items.quantity'] } },
ordersCount: { $sum: 1 }
}
}
]);
باز کردن چند آرایه
db.collection('restaurants').aggregate([
{ $unwind: '$categories' },
{ $unwind: '$reviews' },
{
$group: {
_id: '$categories',
averageRating: { $avg: '$reviews.rating' },
reviewCount: { $sum: 1 }
}
}
]);
اسناد گروهی
را $group عملگر در MongoDB برای گروه بندی اسناد توسط یک کلید مشخص استفاده می شود.
db.collection('orders').aggregate([
{ $group: { _id: '$cust_id', total: { $sum: '$amount' } } }
]);
گروه و شمارش
db.collection('orders').aggregate([
{ $group: { _id: '$status', count: { $sum: 1 } } }
]);
گروه و جمع
db.collection('orders').aggregate([
{ $group: { _id: '$status', total: { $sum: '$amount' } } }
]);
گروه و میانگین
db.collection('orders').aggregate([
{ $group: { _id: '$status', average: { $avg: '$amount' } } }
]);
گروه و فشار
db.collection('orders').aggregate([
{ $group: { _id: '$cust_id', items: { $push: '$item' } } }
]);
گروه با فیلدهای متعدد
db.collection('orders').aggregate([
{
$group: {
_id: {
status: '$status',
category: '$category'
},
count: { $sum: 1 },
totalAmount: { $sum: '$amount' }
}
}
]);
گروه با عملیات تاریخ
db.collection('orders').aggregate([
{
$group: {
_id: {
year: { $year: '$orderDate' },
month: { $month: '$orderDate' }
},
totalOrders: { $sum: 1 },
revenue: { $sum: '$amount' }
}
}
]);
زمینه های پروژه
را $project عملگر در MongoDB برای گنجاندن، حذف یا تغییر نام فیلدها در اسناد خروجی استفاده می شود.
db.collection('orders').aggregate([
{ $project: { _id: 0, cust_id: 1, amount: 1 } }
]);
پروژه با فیلدهای محاسبه شده
db.collection('orders').aggregate([
{
$project: {
_id: 0,
cust_id: 1,
amount: 1,
discount: { $subtract: ['$total', '$amount'] }
}
}
]);
پروژه با عملیات آرایه
db.collection('orders').aggregate([
{
$project: {
orderId: 1,
itemCount: { $size: '$items' },
firstItem: { $arrayElemAt: ['$items', 0] },
lastItem: { $arrayElemAt: ['$items', -1] },
items: {
$map: {
input: '$items',
as: 'item',
in: {
name: '$$item.name',
subtotal: {
$multiply: ['$$item.price', '$$item.quantity']
}
}
}
}
}
}
]);
پروژه با عملیات رشته
db.collection('users').aggregate([
{
$project: {
fullName: { $concat: ['$firstName', ' ', '$lastName'] },
email: { $toLower: '$email' },
age: { $toString: '$age' }
}
}
]);
پروژه با فیلدهای شرطی
db.collection('users').aggregate([
{
$project: {
name: 1,
status: {
$cond: {
if: { $gte: ['$age', 18] },
then: 'Adult',
else: 'Minor'
}
}
}
}
]);
چندین تجمع را اجرا کنید
شما می توانید چندین خط لوله تجمیع را در یک پرس و جو با استفاده از $facet اپراتور
db.collection('orders').aggregate([
{
$facet: {
totalAmount: [
{ $group: { _id: null, total: { $sum: '$amount' } } }
],
averageAmount: [
{ $group: { _id: null, average: { $avg: '$amount' } } }
]
}
}
]);
معاملات
تراکنش ها در MongoDB به شما این امکان را می دهند که چندین عملیات را به عنوان یک واحد کار، همه یا هیچ انجام دهید. آنها یکپارچگی و سازگاری داده ها را در اسناد و مجموعه های متعدد تضمین می کنند.

ویژگی های تراکنش (ACID)
استفاده از تراکنش ها
برای استفاده از تراکنش ها در MongoDB، معمولاً این مراحل را دنبال کنید:
- یک جلسه را شروع کنید
- یک معامله را شروع کنید
- عملیات را انجام دهد
- معامله را متعهد یا لغو کنید
// Define a client
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
//...
// Start a session
const session = client.startSession();
try {
session.startTransaction();
// Perform multiple operations
await collection1.updateOne({ _id: 1 }, { $set: { status: 'processing' } }, { session });
await collection2.insertOne({ orderId: 1, items: ['item1', 'item2'] }, { session });
// Commit the transaction
await session.commitTransaction();
} catch (error) {
// If an error occurred, abort the transaction
await session.abortTransaction();
console.error('Transaction aborted:', error);
} finally {
// End the session
session.endSession();
}
ملاحظات برای معاملات
- عملکرد: تراکنش ها ممکن است بر عملکرد تأثیر بگذارد، به ویژه برای بارهای کاری سنگین.
- تایم اوت: تراکنش ها دارای مهلت زمانی پیش فرض 60 ثانیه هستند.
- مجموعه های ماکت: تراکنش ها نیاز به پیکربندی مجموعه ماکت دارند.
- خوشه های خرد شده: معاملات روی خوشه های خرد شده ملاحظات و محدودیت های بیشتری دارند.

با استفاده از تراکنشها، میتوانید از یکپارچگی و یکپارچگی دادهها در چندین عملیات در MongoDB اطمینان حاصل کنید، بهویژه زمانی که با مدلهای داده پیچیده یا منطق تجاری مهم سروکار دارید.
مجموعه های ماکت
مجموعه replica گروهی از نمونه های MongoDB است که مجموعه داده های یکسانی را حفظ می کند. مجموعههای Replica افزونگی و در دسترس بودن بالا را فراهم میکنند.
اجزای یک مجموعه ماکت
- اولیه: تمام عملیات نوشتن را دریافت می کند.
- ثانویه: داده های اولیه را تکرار می کند. می تواند برای عملیات خواندن استفاده شود.
- داور: در انتخابات مقدماتی شرکت می کند اما داده ای ندارد.

پیکربندی مجموعه کپی
برای پیکربندی یک مجموعه کپی، از rs.initiate() روش
rs.initiate({
_id: 'rs0',
members: [
{ _id: 0, host: 'mongo1:27017' },
{ _id: 1, host: 'mongo2:27017' },
{ _id: 2, host: 'mongo3:27017', arbiterOnly: true }
]
});
اولویت را بخوانید
اولویت خواندن در MongoDB نحوه توزیع عملیات خواندن در مجموعه replica را تعیین می کند.

- اولیه: از ابتدایی می خواند.
- ثانویه: از ثانویه می خواند.
- Primary Preferred: در صورت وجود از اولیه می خواند، در غیر این صورت از ثانویه.
- ثانویه ترجیح داده شده است: در صورت موجود بودن از ثانویه می خواند، در غیر این صورت از اولیه می خواند.
- نزدیکترین: از نزدیکترین عضو مجموعه ماکت می خواند.
db.collection('users').find().readPref('secondary');
نگرانی را بنویسید
نگرانی نوشتن در MongoDB تعیین کننده سطح تایید برای عملیات نوشتن است.

- w: 0: بدون تایید
- w: 1: قدردانی از ابتدایی.
- w: اکثریت: تصدیق اکثریت مجموعه ماکت.
db.collection('users').insertOne({ name: 'Alice' }, { writeConcern: { w: 'major
Failover خودکار
MongoDB از مکانیزم ضربان قلب برای تشخیص در دسترس بودن اعضای مجموعه replica استفاده می کند. اگر دوره اولیه در دسترس نباشد، یک مقدماتی جدید انتخاب می شود.
-
Primaryبر اساس تعداد آرای اعضای مجموعه ماکت انتخاب می شود. -
Secondaryدر صورتی که اولیه در دسترس نباشد می توان به ابتدایی ارتقا داد. -
Arbiterبرای شکستن تساوی در انتخابات استفاده می شود.

Failover دستی
شما می توانید با وادار کردن یک عضو مجموعه replica برای تبدیل شدن به اصلی، یک Failover دستی را در MongoDB آغاز کنید.
rs.stepDown();
شاردینگ
Sharding روشی برای توزیع داده ها در چندین ماشین است. این به شما امکان می دهد با افزودن ماشین های بیشتر به سیستم خود، مقیاس افقی را انجام دهید.
یک مجموعه به تکههایی تقسیم میشود و هر تکه روی یک قطعه متفاوت ذخیره میشود.
هر Shard زیرمجموعه ای از داده ها در یک خوشه خرد شده است.

اجزای شاردینگ
- شارد: زیر مجموعه ای از داده ها در یک خوشه خرد شده.
- سرور پیکربندی: فوق داده ها و تنظیمات پیکربندی را برای خوشه ذخیره می کند.
- کوئری روتر: کوئری ها را به قطعه مناسب هدایت می کند.

کلید شاردینگ
کلید اشتراک گذاری فیلدی است که برای توزیع داده ها در بین قطعات استفاده می شود. برای اطمینان از توزیع متعادل داده ها باید با دقت انتخاب شود.
db.collection.createIndex({ _id: 'hashed' });
هنگام انتخاب یک کلید خرد، فاکتورهای زیر را در نظر بگیرید:
- کاردینالیته: تعداد مقادیر منحصر به فرد در کلید خرده.
- مقیاس بندی را بنویسید: توانایی توزیع عملیات نوشتن در بین خرده ها.
- جداسازی پرس و جو: توانایی هدف قرار دادن خرده های خاص برای عملیات خواندن.
استراتژی های کلید شارد
- Hashed Sharding: با استفاده از یک تابع هش، داده ها را به طور یکنواخت در بین خرده ها توزیع می کند.
- Range Sharding: داده ها را بر اساس محدوده ای از مقادیر در کلید خرده توزیع می کند.
- شاردینگ مرکب: داده ها را بر اساس چند فیلد در کلید خرده توزیع می کند.
db.collection.createIndex({ _id: 'hashed' });
db.collection.createIndex({ date: 1 });
db.collection.createIndex({ country: 1, city: 1 });
مانگوس
Mongoose یک کتابخانه Object Data Modeling (ODM) برای MongoDB و Node.js است. این یک راه حل مبتنی بر طرحواره برای مدل سازی داده های برنامه شما ارائه می دهد.

اتصال به MongoDB
برای اتصال به MongoDB با استفاده از Mongoose، از connect() روش
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/myapp', { useNewUrlParser: true, useUnifiedTopology: true });
تعریف طرحواره
طرحواره Mongoose ساختار اسناد را در یک مجموعه تعریف می کند.
const userSchema = new mongoose.Schema({
name: String,
age: Number
});
ایجاد یک مدل
مدل Mongoose کلاسی است که مجموعه ای را در MongoDB نشان می دهد.
const User = mongoose.model('User', userSchema);
درج اسناد
برای درج یک سند در یک مجموعه، یک نمونه از مدل ایجاد می کنید و آن را فراخوانی می کنید save() روش
const user = new User({ name: 'Alice', age: 25 });
user.save();
استعلام اسناد
برای درخواست اسناد از یک مجموعه، از find() روش
User.find({ name: 'Alice' });
با پروجکشن:
User.find({ name: 'Alice' }, { name: 1, age: 1 });
به روز رسانی اسناد
برای به روز رسانی اسناد در یک مجموعه، از updateOne() روش
User.updateOne({ name: 'Alice' }, { age: 26 });
حذف اسناد
برای حذف اسناد از مجموعه، از deleteOne() روش
User.deleteOne({ name: 'Alice' });
میان افزار
میان افزار Mongoose توابعی هستند که قبل یا بعد از عملیات خاصی اجرا می شوند.
userSchema.pre('save', function(next) {
console.log('Saving user...');
next();
});
مجازی
مجازیهای Mongoose ویژگیهای سندی هستند که میتوانید آنها را دریافت و تنظیم کنید، اما در MongoDB حفظ نمیشوند.
userSchema.virtual('fullName').get(function() {
return this.name + ' ' + this.age;
});
پلاگین ها
پلاگین های Mongoose قطعاتی از میان افزار طرحواره قابل استفاده مجدد هستند که می توانند به هر طرحی اضافه شوند.
const timestampPlugin = require('./plugins/timestamp');
userSchema.plugin(timestampPlugin);
معاملات
معاملات Mongoose به شما این امکان را می دهد که چندین عملیات را روی چندین سند در یک تراکنش انجام دهید.
const session = await mongoose.startSession();
session.startTransaction();
try {
await User.create({ name: 'Alice' }, { session });
await User.create({ name: 'Bob' }, { session });
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
} finally {
session.endSession();
}
تجمیع
Mongoose یک API روان برای ساخت خطوط لوله تجمیع ارائه می دهد.
const result = await User.aggregate([
{ $match: { name: 'Alice' } },
{ $group: { _id: '$name', total: { $sum: '$age' } }
]);
شاخص ها
Mongoose به شما این امکان را می دهد که شاخص ها را روی طرحواره های خود تعریف کنید.
userSchema.index({ name: 1 });
جمعیت
جمعیت Mongoose به شما امکان می دهد به اسناد موجود در مجموعه های دیگر مراجعه کنید.
const userSchema = new mongoose.Schema({
name: String,
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
});
اعتبار سنجی
Mongoose اعتبار سنجی داخلی را برای فیلدهای طرحواره فراهم می کند.
const userSchema = new mongoose.Schema({
name: { type: String, required: true }
});
الگوها
| الگو | توضیحات | استفاده از مورد | مزایا | معایب |
|---|---|---|---|---|
| الگوی سطل | اسناد مرتبط را در “سطل ها” یا آرایه هایی با اندازه ثابت گروه بندی می کند | داده های سری زمانی، خوانش حسگر اینترنت اشیا | – تعداد اسناد را کاهش می دهد – عملکرد پرس و جو را برای اسکن محدوده بهبود می بخشد |
– مجتمع برای به روز رسانی آیتم های فردی – ممکن است منجر به رشد سند شود |
| الگوی صفت | مجموعه ای از فیلدها را با الگوهای دسترسی مشابه به عنوان یک سند جاسازی شده ذخیره می کند | محصولات با ویژگی های متفاوت | – طرحواره انعطاف پذیر – پرس و جو کارآمد از ویژگی های رایج |
– پرس و جوهای پیچیده تر برای ویژگی های خاص – پتانسیل برای زمینه های استفاده نشده |
| الگوی پرت | داده های رایج را در یک مجموعه و داده های کمیاب و بزرگ را در مجموعه دیگر ذخیره می کند | پست های رسانه های اجتماعی با سطوح تعامل متفاوت | – برای عملکرد کیس معمولی بهینه می شود – از مسائل مربوط به اندازه سند جلوگیری می کند |
– برای موارد پرت به دو پرس و جو نیاز دارد – منطق برنامه پیچیده تر |
| الگوی زیر مجموعه | زیر مجموعه ای از فیلدها را از یک سند در مجموعه ای جداگانه ذخیره می کند | نمایههای کاربر با فیلدهای پربازدید | – عملکرد خواندن را برای پرس و جوهای رایج بهبود می بخشد – اندازه مجموعه کاری را کاهش می دهد |
– تکرار داده ها – نیاز به همگام نگه داشتن زیر مجموعه ها دارد |
پرسش و پاسخ
Exec() در Mongoose چیست؟
را exec() تابع در Mongoose برای اجرای یک پرس و جو و بازگشت یک وعده استفاده می شود. این به شما امکان می دهد تا متدهای پرس و جو را زنجیره ای کنید و سپس در پایان پرس و جو را اجرا کنید.
User.find({ name: 'Alice' }).exec();
می توانید پرس و جو را بدون آن اجرا کنید exec()، با پاسخ به تماس یا استفاده از async/wait.
User.find({ name: 'Alice' }, (error, users) => {
console.log(users);
});
const users = await User.find({ name: 'Alice' });
چه فرقی با هم دارند findOne() و find() در مونگوس؟
-
find(): آرایه ای از تمام اسنادی را برمی گرداند که با معیارهای پرس و جو مطابقت دارند. -
findOne(): اولین سندی را برمی گرداند که با معیارهای پرس و جو مطابقت دارد.
تفاوت بین Model.create() و new Model().save() در Mongoose چیست؟
-
Model.create(): یک سند جدید ایجاد می کند و آن را در یک مرحله در پایگاه داده ذخیره می کند.
User.create({ name: 'Alice' });
-
new Model().save(): نمونه جدیدی از مدل را تکرار می کند اما بلافاصله آن را در پایگاه داده ذخیره نمی کند. قبل از فراخوانی .save() می توانید نمونه را تغییر دهید، اعتبارسنجی انجام دهید یا هر عملیات دیگری را اجرا کنید تا تغییرات ادامه یابد.
const doc = new Model({ name: 'John', age: 30 });
doc.age = 31; // Modify the document
await doc.save(); // Save the document after modification
هدف از متد lean() در کوئری های Mongoose چیست و چه زمانی باید از آن استفاده کرد؟
را lean() متد در جستارهای Mongoose، اشیاء جاوا اسکریپت ساده را به جای اسناد Mongoose برمی گرداند که دارای ویژگی های اضافی زیادی هستند، مانند دریافت کننده ها، تنظیم کننده ها و روش هایی که برای کار با سند مفید هستند. زمانی که به ویژگیهای کامل سند Mongoose نیاز ندارید و میخواهید عملکرد پرس و جو را بهبود ببخشید، باید از آن استفاده کنید.
User.find({ name: 'Alice' }).lean();
چگونه حذف های نرم را در Mongoose پیاده سازی کنیم؟
حذف های نرم در Mongoose شامل علامت گذاری اسناد به عنوان حذف شده به جای حذف فیزیکی آنها از پایگاه داده است. با افزودن a می توانید به این هدف برسید deleted را در طرحواره خود وارد کنید و هنگام حذف سند، آن را روی true تنظیم کنید.
const userSchema = new mongoose.Schema({
name: String,
deleted: { type: Boolean, default: false }
});
از پیش میان افزار برای حذف اسناد حذف شده از نتایج پرس و جو استفاده کنید.
userSchema.pre(/^find/, function(next) {
this.where({ deleted: false });
next();
});
یک روش برای “حذف نرم” یک سند اضافه کنید.
userSchema.methods.softDelete = function() {
this.deleted = true;
return this.save();
};

