跳到主要内容

Express数据库集成

1. 数据库集成概述

在构建Web应用程序时,数据库是存储、检索和管理数据的关键组件。Express本身不提供数据库集成功能,但它可以轻松地与各种数据库系统集成,包括关系型数据库(如MySQL、PostgreSQL)和NoSQL数据库(如MongoDB、Redis)。

选择合适的数据库取决于多种因素:

  • 数据结构和关系复杂性
  • 性能需求
  • 可扩展性要求
  • 开发团队的熟悉程度
  • 部署环境限制

2. 与MongoDB集成

MongoDB是一个流行的NoSQL文档数据库,非常适合与Express一起使用。

2.1 安装依赖

npm install mongoose --save

2.2 配置数据库连接

创建config/database.js

const mongoose = require('mongoose')

const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})

console.log(`MongoDB Connected: ${conn.connection.host}`)
} catch (error) {
console.error(`Error: ${error.message}`)
process.exit(1)
}
}

module.exports = connectDB

在应用程序中使用:

const express = require('express')
const dotenv = require('dotenv')
const connectDB = require('./config/database')

// 加载环境变量
dotenv.config()

// 连接数据库
connectDB()

const app = express()

// ... 其他配置和路由 ...

const PORT = process.env.PORT || 3000
app.listen(PORT, console.log(`Server running on port ${PORT}`))

2.3 创建数据模型

创建models/User.js

const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, '请添加用户名'],
trim: true
},
email: {
type: String,
required: [true, '请添加邮箱'],
unique: true,
trim: true,
match: [
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
'请输入有效的邮箱地址'
]
},
password: {
type: String,
required: [true, '请添加密码'],
minlength: 6
},
createdAt: {
type: Date,
default: Date.now
}
})

module.exports = mongoose.model('User', userSchema)

2.4 在路由中使用模型

创建controllers/userController.js

const User = require('../models/User')

// @desc 获取所有用户
// @route GET /api/users
// @access 公开
const getUsers = async (req, res, next) => {
try {
const users = await User.find()
res.status(200).json({
success: true,
count: users.length,
data: users
})
} catch (error) {
res.status(500).json({
success: false,
error: '服务器错误'
})
}
}

// @desc 创建用户
// @route POST /api/users
// @access 公开
const createUser = async (req, res, next) => {
try {
const user = await User.create(req.body)
res.status(201).json({
success: true,
data: user
})
} catch (error) {
res.status(400).json({
success: false,
error: error.message
})
}
}

module.exports = {
getUsers,
createUser
}

创建routes/userRoutes.js

const express = require('express')
const router = express.Router()
const { getUsers, createUser } = require('../controllers/userController')

router
.route('/')
.get(getUsers)
.post(createUser)

module.exports = router

在应用程序中注册路由:

const express = require('express')
const app = express()

// 解析JSON请求体
app.use(express.json())

// 挂载路由
app.use('/api/users', require('./routes/userRoutes'))

// ... 其他配置 ...

3. 与MySQL集成

MySQL是一个流行的关系型数据库管理系统,适用于需要复杂事务和关系查询的应用程序。

3.1 安装依赖

npm install mysql2 sequelize --save

3.2 配置数据库连接

创建config/database.js

const Sequelize = require('sequelize')

// 创建Sequelize实例
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql',
port: process.env.DB_PORT || 3306,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
}
)

// 测试连接
sequelize
.authenticate()
.then(() => {
console.log('MySQL连接成功')
})
.catch(err => {
console.error('无法连接到MySQL数据库:', err)
})

module.exports = sequelize

3.3 创建数据模型

创建models/User.js

const Sequelize = require('sequelize')
const sequelize = require('../config/database')

const User = sequelize.define('User', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
name: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: Sequelize.STRING,
allowNull: false
}
}, {
timestamps: true
})

module.exports = User

3.4 同步数据库模型

const sequelize = require('./config/database')
const User = require('./models/User')

// 同步所有模型
sequelize.sync()
.then(() => {
console.log('数据库模型同步成功')
})
.catch(err => {
console.error('数据库模型同步失败:', err)
})

3.5 在路由中使用模型

const express = require('express')
const router = express.Router()
const User = require('../models/User')

// 获取所有用户
router.get('/', async (req, res) => {
try {
const users = await User.findAll()
res.status(200).json(users)
} catch (error) {
res.status(500).json({ error: error.message })
}
})

// 创建新用户
router.post('/', async (req, res) => {
try {
const user = await User.create(req.body)
res.status(201).json(user)
} catch (error) {
res.status(400).json({ error: error.message })
}
})

module.exports = router

4. 与Redis集成

Redis是一个开源的内存数据结构存储,可用作数据库、缓存和消息代理。

4.1 安装依赖

npm install redis --save

4.2 配置Redis连接

创建config/redis.js

const redis = require('redis')

const client = redis.createClient({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD
})

client.on('connect', () => {
console.log('已连接到Redis服务器')
})

client.on('error', (err) => {
console.error('Redis连接错误:', err)
})

module.exports = client

4.3 使用Redis作为缓存

创建缓存中间件middlewares/cache.js

const redisClient = require('../config/redis')

const cache = (duration) => {
return (req, res, next) => {
const key = req.originalUrl

redisClient.get(key, (err, data) => {
if (err) throw err

if (data !== null) {
// 命中缓存
res.send(JSON.parse(data))
} else {
// 重写res.send方法,将响应缓存
const originalSend = res.send
res.send = function(body) {
// 缓存响应数据
redisClient.setex(key, duration, body)
originalSend.call(this, body)
}
next()
}
})
}
}

module.exports = cache

在路由中使用缓存中间件:

const express = require('express')
const router = express.Router()
const User = require('../models/User')
const cache = require('../middlewares/cache')

// 获取所有用户(添加5分钟缓存)
router.get('/', cache(300), async (req, res) => {
try {
const users = await User.find()
res.status(200).json({
success: true,
data: users
})
} catch (error) {
res.status(500).json({
success: false,
error: '服务器错误'
})
}
})

module.exports = router

5. 数据库连接池管理

数据库连接池是一组预先创建的数据库连接,这些连接可以被重用,而不是在每次请求时创建和销毁新的连接。

5.1 MongoDB连接池

Mongoose(MongoDB的ODM)默认使用连接池,通常不需要额外配置。但可以根据需要调整连接池大小:

const mongoose = require('mongoose')

const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
poolSize: 10, // 最大连接数
serverSelectionTimeoutMS: 5000, // 服务器选择超时
socketTimeoutMS: 45000, // 套接字超时
family: 4 // 使用IPv4,跳过IPv6查找
})

console.log('MongoDB连接成功')
} catch (error) {
console.error('MongoDB连接失败:', error)
process.exit(1)
}
}

5.2 MySQL连接池

使用mysql2包配置连接池:

const mysql = require('mysql2/promise')

const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
})

module.exports = pool

使用连接池执行查询:

const pool = require('../config/database')

const getUsers = async (req, res) => {
try {
const [rows] = await pool.execute('SELECT * FROM users')
res.json(rows)
} catch (error) {
console.error('查询错误:', error)
res.status(500).json({ error: '服务器错误' })
}
}

6. 事务处理

事务是一组要么全部成功要么全部失败的操作,对于确保数据一致性至关重要。

6.1 MongoDB事务

MongoDB 4.0及以上版本支持事务,但需要使用复制集:

const session = await mongoose.startSession()
session.startTransaction()

try {
// 在事务中执行操作
const user = await User.create([{ name: '张三', email: 'zhangsan@example.com' }], { session })

const order = await Order.create([{ userId: user[0]._id, product: '产品A' }], { session })

// 提交事务
await session.commitTransaction()
res.status(201).json({ user, order })
} catch (error) {
// 回滚事务
await session.abortTransaction()
res.status(400).json({ error: error.message })
} finally {
session.endSession()
}

6.2 MySQL事务

const pool = require('../config/database')

const createUserWithProfile = async (req, res) => {
const connection = await pool.getConnection()

try {
// 开始事务
await connection.beginTransaction()

// 执行SQL语句
const [userResult] = await connection.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
[req.body.name, req.body.email]
)

const [profileResult] = await connection.execute(
'INSERT INTO profiles (user_id, bio) VALUES (?, ?)',
[userResult.insertId, req.body.bio]
)

// 提交事务
await connection.commit()
res.status(201).json({ success: true, userId: userResult.insertId })
} catch (error) {
// 回滚事务
await connection.rollback()
res.status(400).json({ error: error.message })
} finally {
// 释放连接
connection.release()
}
}

7. 数据库迁移和种子

数据库迁移是管理数据库架构变更的过程,而种子是填充初始数据的过程。

7.1 使用Sequelize进行MySQL迁移

安装依赖:

npm install sequelize-cli --save-dev

初始化Sequelize CLI:

npx sequelize-cli init

创建迁移文件:

npx sequelize-cli model:generate --name User --attributes name:string,email:string,password:string

运行迁移:

npx sequelize-cli db:migrate

创建种子文件:

npx sequelize-cli seed:generate --name demo-user

运行种子:

npx sequelize-cli db:seed:all

7.2 使用Mongoose进行MongoDB迁移

对于MongoDB,可以使用migrate-mongo包:

npm install migrate-mongo --save-dev
npx migrate-mongo init

编辑migrate-mongo-config.js配置文件,然后创建迁移:

npx migrate-mongo create create-users-collection

运行迁移:

npx migrate-mongo up

8. 数据库性能优化

8.1 查询优化

  • 限制返回字段:只查询需要的字段
  • 分页查询:避免一次性返回大量数据
  • 索引:为常用查询字段添加索引
  • 避免N+1查询问题:使用适当的关联查询

MongoDB中的选择性查询:

// 只返回name和email字段
const users = await User.find({}, 'name email')

// 分页查询
const page = parseInt(req.query.page) || 1
const limit = parseInt(req.query.limit) || 10
const skip = (page - 1) * limit

const users = await User.find()
.skip(skip)
.limit(limit)

MySQL中的选择性查询:

// 只返回name和email字段
const [users] = await pool.execute('SELECT name, email FROM users')

// 分页查询
const page = parseInt(req.query.page) || 1
const limit = parseInt(req.query.limit) || 10
const offset = (page - 1) * limit

const [users] = await pool.execute(
'SELECT * FROM users LIMIT ? OFFSET ?',
[limit, offset]
)

8.2 连接池优化

  • 调整连接池大小以匹配应用需求
  • 设置适当的连接超时
  • 监控连接池使用情况

8.3 缓存策略

  • 对频繁访问但不经常变化的数据使用缓存
  • 设置合理的缓存过期时间
  • 考虑使用Redis等内存数据库进行缓存

9. 数据库安全最佳实践

  1. 使用环境变量存储凭据:不要在代码中硬编码数据库凭据
  2. 实施输入验证:防止SQL注入和NoSQL注入攻击
  3. 使用参数化查询:避免SQL注入
  4. 限制数据库用户权限:遵循最小权限原则
  5. 加密敏感数据:如密码、个人身份信息等
  6. 定期备份数据库:确保数据可恢复性
  7. 使用ORM或ODM:减少手写SQL的安全风险
  8. 实施连接池:防止连接耗尽攻击
  9. 监控数据库访问:检测可疑活动
  10. 保持数据库软件更新:应用安全补丁和更新

10. 练习项目

创建一个具有以下功能的Express应用程序:

  1. 与至少两种数据库集成(MongoDB和MySQL)
  2. 实现完整的CRUD操作
  3. 使用ORM/ODM(Mongoose和Sequelize)
  4. 配置连接池
  5. 实现事务处理
  6. 设置缓存策略
  7. 实施数据库迁移和种子
  8. 优化查询性能
  9. 应用安全最佳实践

通过这个练习,你将掌握Express与各种数据库集成的技术,能够构建数据驱动的Web应用程序。