Overview
大多数应用程序使用单个MongoDB连接。但是,某些应用程序可从多个连接中受益,从而实现数据分离、性能优化或架构要求。
在本教程中,您将学习;了解如何在单个 Node.js应用程序中建立多个MongoDB连接。
多个MongoDB连接的使用案例
多个MongoDB连接对于需要数据分离、提高性能或架构灵活性的应用程序非常有用。
多个MongoDB连接的常见用例包括:
多租户应用程序:每个租户使用具有自己连接的单独数据库,以实现数据隔离性和安全性
微服务架构:每个服务都维护自己的专用数据库连接
负载分布:将写入操作分布到多个数据库实例
数据处理:同时从多个MongoDB服务器检索数据以进行分析或报告
多租户应用程序
考虑一个多租户应用程序,其中不同的租户或客户股票相同的 Web应用程序,但需要单独、隔离的数据库。在这种情况下,每个租户都可以拥有自己专用的MongoDB连接。这确保了每个租户的数据分离、安全性和自定义性,同时所有租户都在同一应用程序中运行。这种方法简化了管理,并提供了一种在新租户加入平台时进行扩展的方法,而不影响现有租户。
在探索如何在应用程序中实现多个连接时,请考虑以下MongoDB概念:
这些概念在需要多个MongoDB连接以实现高效数据管理和性能优化的场景中发挥角色。
Tutorial
在本教程中,您将执行以下操作:
设置环境并安装所需的依赖项
连接至 MongoDB
设置主节点 (primary node in the replica set)MongoDB连接
设置从节点(secondary node from replica set)MongoDB连接
使用现有模式
设置模式灵活性
在同一连接中切换数据库
先决条件
在开始本教程之前,您必须有一个MongoDB Atlas 群集。要学习;了解如何创建免费的MongoDB Atlas 群集,请参阅Atlas入门教程。
注意
本教程使用MongoDB Server 8.0.12 和 Node.js 版本 20.15.1。
安装依赖项
安装本教程所需的依赖项:
npm install express mongoose
本教程使用Express.js 作为 Web框架,使用Mongoose进行MongoDB对象建模。
注意
本教程使用Mongoose,这是一个流行的MongoDB对象数据建模 (ODM) 库。有关Mongoose入门的更多信息,请参阅 Mongoose入门教程。
设置环境变量
在项目根目录中创建 .env
文件来存储MongoDB连接字符串:
touch .env
将以下环境变量添加到 .env
文件中:
PRIMARY_CONN_STR=<your-primary-connection-string> SECONDARY_CONN_STR=<your-secondary-connection-string>
将占位符值替换为实际的MongoDB Atlas连接字符串。要学习;了解如何查找MongoDB Atlas连接字符串,请参阅连接到集群教程。
安装 dotenv
包以加载环境变量:
npm install dotenv
您现在已经有了一个项目,可以在应用程序中设立多个MongoDB连接。
项目结构
完成本教程后,此应用程序的项目目录将具有以下结构:
mongodb-multiple-connections/ ├── .env # Environment variables ├── package.json # Project dependencies ├── package-lock.json # Dependency lock file ├── node_modules/ # Installed packages ├── index.js # Main application file ├── db.primary.js # Primary connection configuration ├── db.secondary.js # Secondary connection configuration └── product.schema.js # Product schema definition
您将在本教程中创建的密钥文件提供服务以下用途:
索引.js :导入和使用这两个连接的主应用程序文件
db.主节点 (primary node in the replica set).js :使用
mongoose.connect()
配置主节点 (primary node in the replica set)MongoDB连接db.从节点(secondary node from replica set).js :使用
mongoose.createConnection()
配置从节点(secondary node from replica set)MongoDB连接产品。模式.js:为两个连接定义可重用产品模式
.env:安全地存储MongoDB连接字符串
设置主节点MongoDB连接
在本节中,学习;了解如何使用Mongoose在应用程序中设立主节点 (primary node in the replica set)MongoDB连接。使用 mongoose.connect()方法为主应用程序建立主节点 (primary node in the replica set)MongoDB 数据库连接。此方法为整个应用程序管理单个连接池。
创建主节点连接文件
在名为 db.primary.js
的新文件中,定义要在主应用程序文件index.js
中使用的连接方法。此方法可配置MongoDB连接并处理事件。将以下代码复制并粘贴到 db.primary.js
文件中:
require('dotenv').config(); const mongoose = require("mongoose"); module.exports = async (uri, options = {}) => { // By default, Mongoose skips properties not defined in the schema (strictQuery). // You can adjust it based on your configuration. mongoose.set('strictQuery', true); // Connect to MongoDB try { await mongoose.connect(uri, options); console.info("MongoDB primary connection initiated"); } catch (err) { console.error("MongoDB primary connection failed, " + err); } // Event handling mongoose.connection.once('open', () => console.info("MongoDB primary connection opened!")); mongoose.connection.on('connected', () => console.info("MongoDB primary connection succeeded!")); mongoose.connection.on('error', (err) => { console.error("MongoDB primary connection failed, " + err); mongoose.disconnect(); }); mongoose.connection.on('disconnected', () => console.info("MongoDB primary connection disconnected!")); // Graceful exit process.on('SIGINT', async () => { try { await mongoose.connection.close(); console.info("Mongoose primary connection disconnected through app termination!"); process.exit(0); } catch (err) { console.error("Error during graceful shutdown:", err); process.exit(1); } }); }
创建产品模式
创建用于在应用程序中执行操作的模式。在名为 product.schema.js
的单独文件中写入模式并将其导出,如以下代码所示:
const mongoose = require("mongoose"); module.exports = (options = {}) => { // Schema for Product return new mongoose.Schema( { store: { _id: mongoose.Types.ObjectId, // Reference-id to the store collection name: String }, name: String, price: Number, category: String, description: String // add required properties }, options ); }
使用主连接
在主 index.js
文件中导入 db.primary.js
文件,并使用其中定义的方法建立主节点 (primary node in the replica set)MongoDB连接。如果需要,还可以传递可选的连接选项对象。
然后,导入 product.schema.js
文件以访问权限产品模式。这使您可以创建模型并执行与应用程序中的产品相关的操作。将以下代码复制并粘贴到 index.js
文件中,以设立主节点 (primary node in the replica set)连接并对示例产品数据执行操作:
// Load environment variables require('dotenv').config(); const mongoose = require("mongoose"); // Async function to establish the primary MongoDB connection async function establishPrimaryConnection() { try { await require("./db.primary.js")(process.env.PRIMARY_CONN_STR, { // (optional) connection options }); } catch (error) { console.error('Failed to establish primary connection:', error); process.exit(1); } } // Initialize connection establishPrimaryConnection(); // Import Product Schema const productSchema = require("./product.schema.js")({ collection: "products", // Pass configuration options if needed }); // Create Model const ProductModel = mongoose.model("Product", productSchema); // Sample products data const sampleProducts = [ { name: "Laptop Pro", price: 1299.99, category: "Electronics", description: "High-performance laptop for professionals" }, { name: "Wireless Headphones", price: 199.99, category: "Electronics", description: "Premium noise-cancelling headphones" }, { name: "Coffee Maker", price: 89.99, category: "Kitchen", description: "Automatic drip coffee maker" } ]; // Wait for connection to be ready before executing operations mongoose.connection.once('open', async () => { console.log('Primary database connected, executing operations...'); try { // Check if products already exist const existingCount = await ProductModel.countDocuments(); console.log(`Existing products in primary DB: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into primary database...'); await ProductModel.insertMany(sampleProducts); console.log('Sample products inserted into primary database!'); } // Find and display a product let product = await ProductModel.findOne(); console.log('Product found in primary DB:', product); // Display all products const allProducts = await ProductModel.find(); console.log(`Total products in primary DB: ${allProducts.length}`); } catch (error) { console.error('Error with primary database operations:', error); } });
Primary database connected, executing operations... MongoDB primary connection initiated Existing products in primary DB: 3 Product found in primary DB: { _id: new ObjectId('...'), name: 'Laptop Pro', __v: 0 } Total products in primary DB: 3
您的应用程序需要多个MongoDB连接。
设置从节点MongoDB连接
您可以为各种使用案例配置从节点(secondary node from replica set)MongoDB连接。在本节中,学习;了解如何设立和使用从节点(secondary node from replica set)连接。
创建从节点连接文件
使用 mongoose.createConnection() 在 文件中创建连接代码db.secondary.js
方法。此方法允许您建立单独的连接池,每个连接池都根据特定使用案例或数据访问模式定制,这与您之前用于主节点 (primary node in the replica set)MongoDB连接的 mongoose.connect()
方法不同:
const mongoose = require("mongoose"); module.exports = (uri, options = {}) => { // Connect to MongoDB const db = mongoose.createConnection(uri, options); // By default, Mongoose skips properties not defined in the schema (strictQuery). // Adjust it based on your configuration. db.set('strictQuery', true); // Event handling db.once('open', () => console.info("MongoDB secondary connection opened!")); db.on('connected', () => console.info(`MongoDB secondary connection succeeded!`)); db.on('error', (err) => { console.error(`MongoDB secondary connection failed, ` + err); db.close(); }); db.on('disconnected', () => console.info(`MongoDB secondary connection disconnected!`)); // Graceful exit process.on('SIGINT', async () => { try { await db.close(); console.info(`Mongoose secondary connection disconnected through app termination!`); process.exit(0); } catch (err) { console.error("Error during graceful shutdown:", err); process.exit(1); } }); // Export db object return db; }
使用从节点连接
在主 index.js
文件中导入 db.secondary.js
文件,使用名为 db
的变量创建连接对象,并使用其中定义的方法建立从节点(secondary node from replica set)MongoDB连接。如果需要,还可以传递可选的连接选项对象。将以下代码添加到 index.js
文件的末尾:
// Load environment variables require('dotenv').config(); // Establish the secondary MongoDB connection const db = require("./db.secondary.js")(process.env.SECONDARY_CONN_STR, { // (optional) connection options }); // Import Product Schema const SecondaryProductSchema = require("./product.schema.js")({ collection: "products", // Pass configuration options if needed }); // Create Model using the secondary connection const SecondaryProductModel = db.model("Product", SecondaryProductSchema); // Sample products data for secondary database const SecondarySampleProducts = [ { name: "Smart Watch", price: 199.99, category: "Electronics", description: "Advanced fitness tracking smartwatch" }, { name: "Bluetooth Speaker", price: 79.99, category: "Electronics", description: "Portable wireless speaker with premium sound" }, { name: "Desk Lamp", price: 49.99, category: "Home", description: "LED desk lamp with adjustable brightness" } ]; // Wait for secondary connection to be ready before executing operations db.once('open', async () => { console.log('Secondary database connected, executing operations...'); try { // Check if products already exist const existingCount = await SecondaryProductModel.countDocuments(); console.log(`Existing products in secondary DB: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into secondary database...'); await SecondaryProductModel.insertMany(SecondarySampleProducts); console.log('Sample products inserted into secondary database!'); } // Find and display a product let product = await SecondaryProductModel.findOne(); console.log('Product found in secondary DB:', product); // Display all products const allProducts = await SecondaryProductModel.find(); console.log(`Total products in secondary DB: ${allProducts.length}`); } catch (error) { console.error('Error with secondary database operations:', error); } });
Primary database connected, executing operations... MongoDB primary connection initiated MongoDB secondary connection succeeded! MongoDB secondary connection opened! Secondary database connected, executing operations... Existing products in primary DB: 3 Existing products in secondary DB: 6 Product found in primary DB: { _id: new ObjectId('...'), name: 'Laptop Pro', __v: 0 } Product found in secondary DB: { _id: new ObjectId('...'), name: 'Smart Watch', __v: 0 } Total products in primary DB: 3 Total products in secondary DB: 3
现在,您已经设立了连接,可以使用新的 db
对象来创建模型。以下部分探讨了不同的场景和示例,以帮助您选择最适合您的特定数据访问和管理需求的设置。
使用现有模式
如果两个连接在同一数据模型上运行,请使用在主节点 (primary node in the replica set)连接中使用的相同 product.schema.js
文件。
导入 product.schema.js
文件以访问权限产品模式。这样,您就可以使用 db
对象创建模型,并执行与应用程序中的产品相关的操作:
// Import Product Schema const secondaryProductSchema = require("./product.schema.js")({ collection: "products", // Pass configuration options if needed }); // Create Model const SecondaryProductModel = db.model("Product", secondaryProductSchema); // Wait for secondary connection to be ready before executing operations db.once('open', async () => { console.log('Secondary database connected, executing operations...'); try { // Check if products already exist in secondary database const existingCount = await SecondaryProductModel.countDocuments(); console.log(`Existing products in secondary DB: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into secondary database...'); await SecondaryProductModel.insertMany(sampleProducts); console.log('Sample products inserted into secondary database!'); } // Find and display a product let product = await SecondaryProductModel.findOne(); console.log('Product found in secondary DB:', product); // Display all products const allProducts = await SecondaryProductModel.find(); console.log(`Total products in secondary DB: ${allProducts.length}`); } catch (error) { console.error('Error with secondary database operations:', error); } });
要查看在项目中使用主节点 (primary node in the replica set)数据库连接到从从节点(secondary node from replica set)MongoDB连接的现有模式的实际代码示例和可用资源,请访问使用现有模式Github存储库。
设置模式灵活性
使用多个MongoDB连接时,必须根据特定使用案例灵活地调整模式。尽管主节点 (primary node in the replica set)连接可能需要带有验证的严格模式以确保数据完整性,但在某些情况下,从节点(secondary node from replica set)连接有不同的用途。实例,从节点(secondary node from replica set)连接可能会在存档服务器上存储用于分析的数据,并具有由过去使用案例驱动的不同模式要求。在本节中,您将了解如何为从从节点(secondary node from replica set)连接配置模式灵活性,从而满足应用程序的独特需求。
如果您希望在Mongoose中获得模式灵活性,请在为从从节点(secondary node from replica set)连接配置模式时在模式选项中传递 strict: false
属性。这允许您处理不严格遵循模式的数据。
导入 product.schema.js
文件以访问权限产品模式。这样,您就可以使用 db
对象创建模型,并执行与应用程序中的产品相关的操作:
// Import Product Schema const secondaryProductSchema = require("./product.schema.js")({ collection: "products", strict: false // Pass configuration options if needed }); // Create Model const SecondaryProductModel = db.model("Product", secondaryProductSchema); // Wait for secondary connection to be ready before executing operations db.once('open', async () => { console.log('Secondary database (flexible schema) connected, executing operations...'); try { // Check if products already exist in secondary database const existingCount = await SecondaryProductModel.countDocuments(); console.log(`Existing products in secondary DB: ${existingCount}`); if (existingCount === 0) { // Add extra fields to demonstrate schema flexibility const flexibleSampleProducts = sampleProducts.map(product => ({ ...product, extraField: "This field is not in the schema but will be saved due to strict: false", timestamp: new Date() })); console.log('Inserting sample products with extra fields into secondary database...'); await SecondaryProductModel.insertMany(flexibleSampleProducts); console.log('Sample products with extra fields inserted into secondary database!'); } // Find and display a product let product = await SecondaryProductModel.findOne(); console.log('Product found in secondary DB (flexible schema):', product); // Display all products const allProducts = await SecondaryProductModel.find(); console.log(`Total products in secondary DB: ${allProducts.length}`); } catch (error) { console.error('Error with secondary database operations:', error); } });
要查看在项目的从节点(secondary node from replica set)MongoDB连接中设置模式灵活性的实际代码示例和可用资源,请参阅设置模式灵活性Github存储库。
在同一连接中切换数据库
在应用程序的数据库设置中,您可以使用db.useDb()方法在不同数据库之间切换。此方法允许您创建与特定数据库关联的新连接对象,同时共享同一连接池。
这种方法允许您使用单个连接管理应用程序中的多个数据库,同时为每个数据库维护不同的数据上下文。
导入 product.schema.js
文件以访问权限产品模式。这样,您就可以使用 db
对象创建模型,并执行与应用程序中的产品相关的操作。
示例:使用单独数据库进行存储
考虑一个电子商务平台,其中多个商店独立运营,每个存储维护自己的产品管理数据库。使用 db.useDb()
方法在不同存储数据库之间切换,同时保持共享连接池,如以下示例所示:
// Load environment variables require('dotenv').config(); // Establish the secondary MongoDB connection const db = require("./db.secondary.js")(process.env.SECONDARY_CONN_STR, { // (optional) connection options }); // Import Product Schema const secondaryProductSchema = require("./product.schema.js")({ collection: "products", // strict: false // that doesn't adhere strictly to the schema! // Pass configuration options if needed }); // Base sample products data const sampleProducts = [ { name: "Laptop Pro", price: 1299.99, category: "Electronics", description: "High-performance laptop for professionals" }, { name: "Wireless Headphones", price: 199.99, category: "Electronics", description: "Premium noise-cancelling headphones" }, { name: "Coffee Maker", price: 89.99, category: "Kitchen", description: "Automatic drip coffee maker" } ]; // Sample store-specific products const storeAProducts = sampleProducts.map(product => ({ ...product, store: { name: "Store A" }, storeId: "A" })); const storeBProducts = [ { name: "Gaming Chair", price: 299.99, category: "Furniture", description: "Ergonomic gaming chair with RGB lighting", store: { name: "Store B" }, storeId: "B" }, { name: "Mechanical Keyboard", price: 149.99, category: "Electronics", description: "RGB mechanical gaming keyboard", store: { name: "Store B" }, storeId: "B" } ]; // Create a connection for 'Store A' const storeA = db.useDb('StoreA'); // Create Model const SecondaryStoreAProductModel = storeA.model("Product", secondaryProductSchema); // Wait for Store A connection to be ready storeA.once('open', async () => { console.log('Store A database connected, executing operations...'); try { // Check if products already exist in Store A const existingCount = await SecondaryStoreAProductModel.countDocuments(); console.log(`Existing products in Store A: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into Store A database...'); await SecondaryStoreAProductModel.insertMany(storeAProducts); console.log('Sample products inserted into Store A database!'); } // Find and display a product let product = await SecondaryStoreAProductModel.findOne(); console.log('Product found in Store A:', product); // Display all products const allProducts = await SecondaryStoreAProductModel.find(); console.log(`Total products in Store A: ${allProducts.length}`); } catch (error) { console.error('Error with Store A operations:', error); } }); // Create a connection for 'Store B' const storeB = db.useDb('StoreB'); // Create Model const SecondaryStoreBProductModel = storeB.model("Product", secondaryProductSchema); // Wait for Store B connection to be ready storeB.once('open', async () => { console.log('Store B database connected, executing operations...'); try { // Check if products already exist in Store B const existingCount = await SecondaryStoreBProductModel.countDocuments(); console.log(`Existing products in Store B: ${existingCount}`); if (existingCount === 0) { console.log('Inserting sample products into Store B database...'); await SecondaryStoreBProductModel.insertMany(storeBProducts); console.log('Sample products inserted into Store B database!'); } // Find and display a product let product = await SecondaryStoreBProductModel.findOne(); console.log('Product found in Store B:', product); // Display all products const allProducts = await SecondaryStoreBProductModel.find(); console.log(`Total products in Store B: ${allProducts.length}`); } catch (error) { console.error('Error with Store B operations:', error); } });
前面的示例为 Store A
和 Store B
建立了单独的数据库连接,每个存储都包含自己的产品数据。这种方法提供了数据分离,同时利用单个共享连接池进行高效的资源管理。
前面的静态方法使用预定义名称(StoreA
、StoreB
)为每个存储创建显式连接。
对于动态存储管理,创建一个函数,接受存储标识符作为参数并返回连接对象。此功能支持按标识符进行存储切换,并重用现有连接以提高效率。
// Function to get connection object for particular store's database function getStoreConnection(storeId) { return db.useDb("Store"+storeId, { useCache: true }); } // Create a connection for 'Store A' const store = getStoreConnection("A"); // Create Model const SecondaryStoreProductModel = store.model("Product", secondaryProductSchema); // Wait for store connection to be ready store.once('open', async () => { console.log('Store A (dynamic) database connected, executing operations...'); try { // Check if products already exist in the store const existingCount = await SecondaryStoreProductModel.countDocuments(); console.log(`Existing products in Store A (dynamic): ${existingCount}`); if (existingCount === 0) { // Use the same store A products from the previous example console.log('Inserting sample products into Store A (dynamic) database...'); await SecondaryStoreProductModel.insertMany(storeAProducts); console.log('Sample products inserted into Store A (dynamic) database!'); } // Find and display a product let product = await SecondaryStoreProductModel.findOne(); console.log('Product found in Store A (dynamic):', product); // Display all products const allProducts = await SecondaryStoreProductModel.find(); console.log(`Total products in Store A (dynamic): ${allProducts.length}`); } catch (error) { console.error('Error with Store A (dynamic) operations:', error); } });
在动态方法中,根据需要创建和缓存连接实例,从而无需手动管理每个存储的单独连接。对于需要在应用程序中处理多个商店的情况,这种方法可提高灵活性和资源效率。
要查看将同一连接中的数据库切换到项目中从节点(secondary node from replica set)MongoDB连接的实际代码示例和可用资源,请访问Github存储库。
后续步骤
本教程演示如何使用Mongoose在 Node.js应用程序中实现多个MongoDB连接。您学习了如何建立主节点 (primary node in the replica set)和从节点(secondary node from replica set)连接、实现模式灵活性以及管理单个连接池的多个数据库。
这些技术启用数据分离、提高性能和架构灵活性。现在,您可以实现满足特定应用程序要求的连接策略。当您继续使用多个MongoDB连接时,请考虑以下最佳实践和其他资源。
最佳实践
在 Node.js应用程序中实现多个MongoDB连接时,请遵循以下最佳实践:
其他资源
要学习;了解有关Mongoose入门的更多信息,请参阅第三方集成部分中的Mongoose入门教程。
要学习;了解有关将Mongoose与MongoDB结合使用的更多信息,请参阅Mongoose文档。
要学习;了解有关在 Node.js应用程序中完整实施多个MongoDB连接的更多信息,请参阅Github存储库。