Node.js 数据库集成
核心知识点
数据库类型
在 Node.js 开发中,常用的数据库类型包括:
- 关系型数据库:MySQL、PostgreSQL、SQLite、Oracle 等
- NoSQL 数据库:MongoDB、Redis、Cassandra、CouchDB 等
数据库连接
连接数据库的基本步骤:
- 安装相应的数据库驱动或客户端
- 配置连接参数(主机、端口、用户名、密码、数据库名等)
- 建立连接
- 执行查询或操作
- 处理结果
- 关闭连接(或使用连接池)
连接池
连接池是一种管理数据库连接的机制,它可以:
- 减少连接建立和关闭的开销
- 控制并发连接数
- 提高应用性能
ORM 工具
ORM(对象关系映射)工具可以:
- 将数据库表映射为 JavaScript 对象
- 提供面向对象的方式操作数据库
- 简化数据库操作代码
- 支持多种数据库
常用的 ORM 工具:
- Sequelize:支持 MySQL、PostgreSQL、SQLite、MSSQL 等
- Mongoose:专门用于 MongoDB
- TypeORM:TypeScript 支持的 ORM
- Prisma:现代 Node.js ORM
事务处理
事务是一组原子性的数据库操作,要么全部成功,要么全部失败。在 Node.js 中,事务处理通常包括:
- 开始事务
- 执行一系列操作
- 提交事务(如果全部成功)
- 回滚事务(如果有任何失败)
实用案例
案例一:MySQL 数据库集成
// 1. 安装依赖
// npm install mysql2
const mysql = require('mysql2');
// 创建连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 执行查询
function getUsers() {
return new Promise((resolve, reject) => {
pool.query('SELECT * FROM users', (error, results) => {
if (error) {
return reject(error);
}
resolve(results);
});
});
}
function getUserById(id) {
return new Promise((resolve, reject) => {
pool.query('SELECT * FROM users WHERE id = ?', [id], (error, results) => {
if (error) {
return reject(error);
}
resolve(results[0]);
});
});
}
function createUser(user) {
return new Promise((resolve, reject) => {
pool.query('INSERT INTO users SET ?', user, (error, results) => {
if (error) {
return reject(error);
}
resolve({ id: results.insertId, ...user });
});
});
}
// 使用示例
async function main() {
try {
// 获取所有用户
const users = await getUsers();
console.log('所有用户:', users);
// 根据 ID 获取用户
const user = await getUserById(1);
console.log('用户 ID 1:', user);
// 创建新用户
const newUser = await createUser({ name: '张三', email: 'zhangsan@example.com' });
console.log('新创建的用户:', newUser);
} catch (error) {
console.error('错误:', error);
} finally {
// 关闭连接池
pool.end();
}
}
main();案例二:MongoDB 数据库集成
// 1. 安装依赖
// npm install mongodb
const { MongoClient } = require('mongodb');
// MongoDB 连接 URL
const url = 'mongodb://localhost:27017';
const dbName = 'testdb';
// 连接数据库
async function connectDB() {
const client = new MongoClient(url);
try {
await client.connect();
console.log('连接到 MongoDB 成功');
const db = client.db(dbName);
return { db, client };
} catch (error) {
console.error('连接到 MongoDB 失败:', error);
throw error;
}
}
// 操作示例
async function main() {
let client;
try {
const { db, client: mongoClient } = await connectDB();
client = mongoClient;
// 获取集合
const usersCollection = db.collection('users');
// 插入文档
const insertResult = await usersCollection.insertOne({
name: '李四',
email: 'lisi@example.com',
age: 25,
createdAt: new Date()
});
console.log('插入结果:', insertResult);
// 查询文档
const users = await usersCollection.find({}).toArray();
console.log('所有用户:', users);
// 根据条件查询
const user = await usersCollection.findOne({ name: '李四' });
console.log('用户 李四:', user);
// 更新文档
const updateResult = await usersCollection.updateOne(
{ _id: user._id },
{ $set: { age: 26 } }
);
console.log('更新结果:', updateResult);
// 删除文档
const deleteResult = await usersCollection.deleteOne({ _id: user._id });
console.log('删除结果:', deleteResult);
} catch (error) {
console.error('错误:', error);
} finally {
if (client) {
await client.close();
console.log('MongoDB 连接已关闭');
}
}
}
main();案例三:使用 Sequelize ORM
// 1. 安装依赖
// npm install sequelize mysql2
const { Sequelize, DataTypes } = require('sequelize');
// 创建 Sequelize 实例
const sequelize = new Sequelize('testdb', 'root', 'password', {
host: 'localhost',
dialect: 'mysql',
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
}
});
// 定义模型
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
age: {
type: DataTypes.INTEGER,
allowNull: true
}
}, {
tableName: 'users',
timestamps: true
});
// 同步模型到数据库
async function syncModels() {
try {
await sequelize.sync({ alter: true });
console.log('模型同步成功');
} catch (error) {
console.error('模型同步失败:', error);
throw error;
}
}
// 操作示例
async function main() {
try {
// 同步模型
await syncModels();
// 创建用户
const user = await User.create({
name: '王五',
email: 'wangwu@example.com',
age: 30
});
console.log('创建的用户:', user.toJSON());
// 查询所有用户
const users = await User.findAll();
console.log('所有用户:', users.map(u => u.toJSON()));
// 根据 ID 查询
const foundUser = await User.findByPk(1);
console.log('用户 ID 1:', foundUser ? foundUser.toJSON() : '未找到');
// 根据条件查询
const filteredUsers = await User.findAll({
where: {
age: {
[Sequelize.Op.gte]: 25
}
}
});
console.log('年龄大于等于 25 的用户:', filteredUsers.map(u => u.toJSON()));
// 更新用户
if (foundUser) {
await foundUser.update({ age: 31 });
console.log('更新后的用户:', foundUser.toJSON());
}
// 删除用户
if (user) {
await user.destroy();
console.log('用户已删除');
}
} catch (error) {
console.error('错误:', error);
} finally {
// 关闭连接
await sequelize.close();
console.log('数据库连接已关闭');
}
}
main();案例四:使用 Mongoose ORM
// 1. 安装依赖
// npm install mongoose
const mongoose = require('mongoose');
// 连接 MongoDB
async function connectDB() {
try {
await mongoose.connect('mongodb://localhost:27017/testdb', {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('连接到 MongoDB 成功');
} catch (error) {
console.error('连接到 MongoDB 失败:', error);
throw error;
}
}
// 定义模式
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true
},
age: {
type: Number,
min: 0
},
createdAt: {
type: Date,
default: Date.now
}
});
// 创建模型
const User = mongoose.model('User', userSchema);
// 操作示例
async function main() {
try {
// 连接数据库
await connectDB();
// 创建用户
const user = new User({
name: '赵六',
email: 'zhaoliu@example.com',
age: 28
});
const savedUser = await user.save();
console.log('创建的用户:', savedUser);
// 查询所有用户
const users = await User.find();
console.log('所有用户:', users);
// 根据条件查询
const foundUser = await User.findOne({ name: '赵六' });
console.log('用户 赵六:', foundUser);
// 更新用户
if (foundUser) {
foundUser.age = 29;
const updatedUser = await foundUser.save();
console.log('更新后的用户:', updatedUser);
}
// 删除用户
if (savedUser) {
await User.deleteOne({ _id: savedUser._id });
console.log('用户已删除');
}
} catch (error) {
console.error('错误:', error);
} finally {
// 关闭连接
await mongoose.disconnect();
console.log('MongoDB 连接已关闭');
}
}
main();案例五:事务处理
// MySQL 事务示例
const mysql = require('mysql2/promise');
async function main() {
let connection;
try {
// 创建连接
connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
});
// 开始事务
await connection.beginTransaction();
console.log('事务开始');
// 执行第一个操作
const [insertResult] = await connection.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
['事务测试', 'transaction@example.com']
);
const userId = insertResult.insertId;
console.log('插入用户 ID:', userId);
// 执行第二个操作
await connection.execute(
'INSERT INTO orders (user_id, product_name, amount) VALUES (?, ?, ?)',
[userId, '测试产品', 100]
);
console.log('插入订单成功');
// 提交事务
await connection.commit();
console.log('事务提交成功');
} catch (error) {
console.error('错误:', error);
// 回滚事务
if (connection) {
await connection.rollback();
console.log('事务回滚成功');
}
} finally {
if (connection) {
await connection.end();
console.log('数据库连接已关闭');
}
}
}
main();案例六:PostgreSQL 数据库集成
// 1. 安装依赖
// npm install pg
const { Client } = require('pg');
// 创建客户端
const client = new Client({
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'password',
database: 'testdb'
});
// 连接数据库
async function connectDB() {
try {
await client.connect();
console.log('连接到 PostgreSQL 成功');
} catch (error) {
console.error('连接到 PostgreSQL 失败:', error);
throw error;
}
}
// 操作示例
async function main() {
try {
await connectDB();
// 创建表
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
age INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
console.log('表创建成功');
// 插入数据
const insertResult = await client.query(
'INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING *',
['钱七', 'qianqi@example.com', 32]
);
console.log('插入的用户:', insertResult.rows[0]);
// 查询数据
const selectResult = await client.query('SELECT * FROM users');
console.log('所有用户:', selectResult.rows);
// 更新数据
const updateResult = await client.query(
'UPDATE users SET age = $1 WHERE name = $2 RETURNING *',
[33, '钱七']
);
console.log('更新后的用户:', updateResult.rows[0]);
// 删除数据
const deleteResult = await client.query(
'DELETE FROM users WHERE name = $1',
['钱七']
);
console.log('删除的行数:', deleteResult.rowCount);
} catch (error) {
console.error('错误:', error);
} finally {
// 关闭连接
await client.end();
console.log('PostgreSQL 连接已关闭');
}
}
main();学习目标
- 掌握数据库连接:能够连接各种类型的数据库
- 使用连接池:学会配置和使用数据库连接池
- 执行基本查询:能够执行 SELECT、INSERT、UPDATE、DELETE 等操作
- 使用 ORM 工具:掌握 Sequelize、Mongoose 等 ORM 工具的使用
- 处理事务:学会使用事务处理保证数据一致性
- 错误处理:能够正确处理数据库操作中的错误
- 性能优化:了解数据库操作的性能优化技巧
代码优化建议
1. 使用环境变量存储数据库配置
不好的做法:
const mysql = require('mysql2');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password', // 硬编码密码
database: 'testdb'
});好的做法:
const mysql = require('mysql2');
require('dotenv').config();
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'testdb'
});2. 使用连接池管理连接
不好的做法:
const mysql = require('mysql2');
// 每次操作都创建新连接
async function getUserById(id) {
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
});
return new Promise((resolve, reject) => {
connection.connect();
connection.query('SELECT * FROM users WHERE id = ?', [id], (error, results) => {
if (error) {
reject(error);
} else {
resolve(results[0]);
}
connection.end();
});
});
}好的做法:
const mysql = require('mysql2');
// 创建连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb',
connectionLimit: 10
});
// 使用连接池执行查询
async function getUserById(id) {
return new Promise((resolve, reject) => {
pool.query('SELECT * FROM users WHERE id = ?', [id], (error, results) => {
if (error) {
reject(error);
} else {
resolve(results[0]);
}
});
});
}3. 错误处理和日志
不好的做法:
app.get('/users', (req, res) => {
pool.query('SELECT * FROM users', (error, results) => {
if (error) {
res.status(500).send('错误');
} else {
res.json(results);
}
});
});好的做法:
app.get('/users', (req, res) => {
pool.query('SELECT * FROM users', (error, results) => {
if (error) {
console.error('查询用户失败:', error);
res.status(500).json({ error: '服务器内部错误' });
} else {
res.json(results);
}
});
});4. 使用参数化查询防止 SQL 注入
不好的做法:
// 容易受到 SQL 注入攻击
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
pool.query(`SELECT * FROM users WHERE id = ${userId}`, (error, results) => {
// 处理结果
});
});好的做法:
// 使用参数化查询
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
pool.query('SELECT * FROM users WHERE id = ?', [userId], (error, results) => {
// 处理结果
});
});5. 异步/await 优化
不好的做法:
// 回调嵌套
pool.query('SELECT * FROM users', (error, users) => {
if (error) {
console.error(error);
} else {
users.forEach(user => {
pool.query('SELECT * FROM orders WHERE user_id = ?', [user.id], (error, orders) => {
if (error) {
console.error(error);
} else {
user.orders = orders;
console.log(user);
}
});
});
}
});好的做法:
// 使用 async/await
const mysql = require('mysql2/promise');
async function getUserWithOrders() {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
});
try {
const [users] = await connection.query('SELECT * FROM users');
for (const user of users) {
const [orders] = await connection.query('SELECT * FROM orders WHERE user_id = ?', [user.id]);
user.orders = orders;
}
return users;
} finally {
await connection.end();
}
}常见问题与解决方案
问题1:数据库连接失败
原因:
- 数据库服务未启动
- 连接参数错误(主机、端口、用户名、密码等)
- 防火墙阻止连接
- 数据库权限不足
解决方案:
- 确保数据库服务已启动
- 检查连接参数是否正确
- 检查防火墙设置
- 确保用户有正确的权限
问题2:SQL 注入攻击
原因:
- 使用字符串拼接构建 SQL 查询
- 未对用户输入进行验证或转义
解决方案:
- 使用参数化查询
- 使用 ORM 工具
- 对用户输入进行验证和转义
问题3:连接泄漏
原因:
- 未正确关闭数据库连接
- 连接池配置不当
解决方案:
- 使用连接池管理连接
- 确保在 finally 块中关闭连接
- 合理配置连接池参数
问题4:事务回滚失败
原因:
- 事务中执行了不支持回滚的操作
- 连接在事务过程中断开
- 代码逻辑错误导致未执行回滚
解决方案:
- 确保事务中只执行支持回滚的操作
- 正确处理错误并执行回滚
- 使用 try/catch/finally 确保回滚逻辑执行
问题5:数据库性能问题
原因:
- 缺少索引
- 查询语句效率低下
- 并发连接数过高
- 数据量过大
解决方案:
- 为常用查询字段添加索引
- 优化查询语句
- 合理配置连接池
- 考虑数据库分片或读写分离
总结
通过本教程的学习,你应该能够:
- 掌握 Node.js 中常见数据库的连接和操作方法
- 使用连接池管理数据库连接,提高应用性能
- 熟练使用 Sequelize、Mongoose 等 ORM 工具简化数据库操作
- 使用事务处理保证数据一致性
- 正确处理数据库操作中的错误
- 了解数据库操作的性能优化技巧
- 安全地处理用户输入,防止 SQL 注入攻击
数据库集成是 Node.js 后端开发的重要组成部分,选择合适的数据库类型和操作方式,对于构建高性能、可靠的应用至关重要。在实际开发中,你应该根据应用的需求和特点,选择最适合的数据库解决方案,并不断优化数据库操作,以提高应用的整体性能和用户体验。