Sequelize 在 Node.js 中的详细用法与使用笔记
1. Sequelize 简介
Sequelize 是一个基于 Promise 的 Node.js ORM (Object-Relational Mapping) 工具,支持 PostgreSQL、MySQL、MariaDB、SQLite 和 Microsoft SQL Server 等多种数据库。
2. 安装与基本配置
安装
npm install sequelize
# 根据使用的数据库安装对应的驱动
npm install pg pg-hstore # PostgreSQL
npm install mysql2 # MySQL
npm install mariadb # MariaDB
npm install sqlite3 # SQLite
npm install tedious # Microsoft SQL Server
基本连接配置
const { Sequelize } = require('sequelize');
// 方法1: 通过URI连接
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname');
// 方法2: 通过参数连接
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql', // 或其他数据库类型
// 其他选项
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
logging: console.log, // 显示日志
// 对于SQLite
// storage: 'path/to/database.sqlite'
});
3. 模型定义
基本模型定义
const { DataTypes } = require('sequelize');
const User = sequelize.define('User', {
// 模型属性
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING
// allowNull 默认为 true
},
age: {
type: DataTypes.INTEGER,
defaultValue: 18
},
bio: {
type: DataTypes.TEXT
},
isAdmin: {
type: DataTypes.BOOLEAN,
defaultValue: false
}
}, {
// 模型选项
tableName: 'users', // 指定表名
timestamps: true, // 默认添加 createdAt 和 updatedAt 字段
// createdAt: 'created_at', // 自定义字段名
// updatedAt: 'updated_at',
// paranoid: true, // 软删除,添加 deletedAt 字段
// underscored: true, // 将驼峰命名转换为下划线
// freezeTableName: true // 防止 Sequelize 自动复数化表名
});
数据类型
Sequelize 提供了多种数据类型:
- STRING / TEXT - 字符串/文本
- INTEGER / BIGINT / FLOAT / DOUBLE / DECIMAL - 数字
- BOOLEAN - 布尔值
- DATE / DATEONLY / TIME - 日期时间
- UUID - UUID
- ENUM - 枚举值
- JSON / JSONB - JSON 数据
- BLOB - 二进制数据
4. 模型同步
// 同步单个模型
await User.sync({ force: true }); // 强制同步,会删除现有表
// 同步所有模型
await sequelize.sync({ alter: true }); // 安全同步,只修改表结构以匹配模型
// 生产环境通常使用迁移而不是 sync()
5. CRUD 操作
创建记录
// 方法1: build + save
const user = User.build({
firstName: 'John',
lastName: 'Doe'
});
await user.save();
// 方法2: create (build + save 的快捷方式)
const user = await User.create({
firstName: 'Jane',
lastName: 'Doe',
age: 25
});
查询记录
// 查找所有记录
const users = await User.findAll();
// 带条件查询
const users = await User.findAll({
where: {
age: {
[Op.gt]: 18 // 大于18岁
}
},
order: [['age', 'DESC']],
limit: 10
});
// 查找单个记录
const user = await User.findOne({
where: { firstName: 'John' }
});
// 通过主键查找
const user = await User.findByPk(1);
// 计数
const count = await User.count({
where: {
age: {
[Op.gt]: 20
}
}
});
更新记录
// 方法1: 先查询再更新
const user = await User.findByPk(1);
if (user) {
user.lastName = 'Smith';
await user.save();
}
// 方法2: 直接更新
await User.update(
{ lastName: 'Smith' },
{
where: {
firstName: 'John'
}
}
);
删除记录
// 方法1: 先查询再删除
const user = await User.findByPk(1);
if (user) {
await user.destroy();
}
// 方法2: 直接删除
await User.destroy({
where: {
age: {
[Op.lt]: 18 // 删除所有年龄小于18的记录
}
}
});
// 软删除 (需要启用 paranoid: true)
await user.destroy(); // 设置 deletedAt
await user.restore(); // 恢复软删除的记录
6. 查询操作符
Sequelize 提供了丰富的查询操作符:
const { Op } = require('sequelize');
// 基本比较
[Op.eq]: 3, // = 3
[Op.ne]: 20, // != 20
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
[Op.lte]: 10, // <= 10
// 范围
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
// 集合
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
// 模糊匹配
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.startsWith]: 'hat', // LIKE 'hat%'
[Op.endsWith]: 'hat', // LIKE '%hat'
[Op.substring]: 'hat', // LIKE '%hat%'
// 逻辑
[Op.and]: [{a: 5}, {b: 6}], // AND (a = 5) AND (b = 6)
[Op.or]: [{a: 5}, {a: 6}], // OR (a = 5 OR a = 6)
[Op.not]: { age: 10 } // NOT (age = 10)
7. 关联关系
一对一关系
// User 有一个 Profile
User.hasOne(Profile);
Profile.belongsTo(User);
// 使用
const user = await User.create({ /* ... */ });
const profile = await Profile.create({ /* ... */ });
await user.setProfile(profile);
const userProfile = await user.getProfile();
一对多关系
// User 有多个 Post
User.hasMany(Post);
Post.belongsTo(User);
// 使用
const user = await User.create({ /* ... */ });
const post1 = await Post.create({ /* ... */ });
const post2 = await Post.create({ /* ... */ });
await user.addPosts([post1, post2]);
const userPosts = await user.getPosts();
多对多关系
// Post 和 Tag 多对多关系
Post.belongsToMany(Tag, { through: 'PostTags' });
Tag.belongsToMany(Post, { through: 'PostTags' });
// 使用
const post = await Post.create({ /* ... */ });
const tag1 = await Tag.create({ name: 'tech' });
const tag2 = await Tag.create({ name: 'programming' });
await post.addTags([tag1, tag2]);
const postTags = await post.getTags();
8. 事务管理
// 手动管理事务
const transaction = await sequelize.transaction();
try {
const user = await User.create({
firstName: 'John',
lastName: 'Doe'
}, { transaction });
await Profile.create({
userId: user.id,
bio: 'Hello world'
}, { transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
}
// 自动管理事务
const result = await sequelize.transaction(async (t) => {
const user = await User.create({
firstName: 'John',
lastName: 'Doe'
}, { transaction: t });
await Profile.create({
userId: user.id,
bio: 'Hello world'
}, { transaction: t });
return user;
});
9. 钩子 (Hooks)
User.beforeCreate(async (user, options) => {
user.password = await hashPassword(user.password);
});
User.afterCreate(async (user, options) => {
await sendWelcomeEmail(user.email);
});
User.beforeUpdate(async (user, options) => {
if (user.changed('password')) {
user.password = await hashPassword(user.password);
}
});
User.afterDestroy(async (user, options) => {
await cleanupUserData(user.id);
});
10. 作用域 (Scopes)
// 定义作用域
User.addScope('active', {
where: {
active: true
}
});
User.addScope('ageGreaterThan', (value) => {
return {
where: {
age: {
[Op.gt]: value
}
}
};
});
// 使用作用域
const activeUsers = await User.scope('active').findAll();
const adults = await User.scope({ method: ['ageGreaterThan', 18] }).findAll();
// 默认作用域
User.init({
// 属性定义
}, {
defaultScope: {
where: {
active: true
}
},
scopes: {
inactive: {
where: {
active: false
}
}
}
});
11. 原始查询
// 不带模型映射的原始查询
const [results, metadata] = await sequelize.query("SELECT * FROM users WHERE age > :age", {
replacements: { age: 18 },
type: QueryTypes.SELECT
});
// 带模型映射的原始查询
const users = await sequelize.query("SELECT * FROM users", {
model: User,
mapToModel: true
});
12. 迁移 (Migrations)
在生产环境中,应该使用迁移而不是 sync() 来管理数据库结构变化。
安装 Sequelize CLI
npm install --save-dev sequelize-cli
npx sequelize-cli init
这会创建以下目录结构:
config/
config.json
migrations/
models/
seeders/
创建迁移
npx sequelize-cli migration:generate --name create-users-table
编辑生成的迁移文件:
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstName: {
type: Sequelize.STRING,
allowNull: false
},
lastName: {
type: Sequelize.STRING
},
age: {
type: Sequelize.INTEGER,
defaultValue: 18
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users');
}
};
运行迁移
npx sequelize-cli db:migrate
回滚迁移
npx sequelize-cli db:migrate:undo
npx sequelize-cli db:migrate:undo:all
13. 数据填充 (Seeders)
npx sequelize-cli seed:generate --name demo-user
编辑生成的种子文件:
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.bulkInsert('Users', [{
firstName: 'John',
lastName: 'Doe',
age: 25,
createdAt: new Date(),
updatedAt: new Date()
}, {
firstName: 'Jane',
lastName: 'Doe',
age: 30,
createdAt: new Date(),
updatedAt: new Date()
}], {});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.bulkDelete('Users', null, {});
}
};
运行种子:
npx sequelize-cli db:seed:all
npx sequelize-cli db:seed --seed name-of-seed-as-in-data
14. 性能优化技巧
批量操作:使用 bulkCreate、update 和 destroy 进行批量操作
选择性查询:只查询需要的字段
User.findAll({
attributes: ['id', 'firstName']
});
分页:使用 limit 和 offset 实现分页
预加载关联数据:使用 include 避免 N+1 查询问题
User.findAll({
include: [{
model: Post,
include: [Tag]
}]
});
使用索引:在经常查询的字段上创建索引
避免在循环中查询:尽可能批量查询
15. 常见问题与解决方案
连接池问题
如果遇到连接池耗尽错误,可以调整连接池配置:
const sequelize = new Sequelize(/* ... */, {
pool: {
max: 20, // 最大连接数
min: 0, // 最小连接数
acquire: 30000, // 获取连接的最大等待时间(毫秒)
idle: 10000 // 连接空闲时间(毫秒),超过此时间连接会被释放
}
});
时区问题
const sequelize = new Sequelize(/* ... */, {
dialectOptions: {
useUTC: false, // 对于MySQL
timezone: '+08:00' // 设置时区
},
timezone: '+08:00' // ORM层时区设置
});
长连接断开
对于长时间空闲的连接可能会被数据库服务器断开,可以设置 keep-alive 查询:
setInterval(() => {
sequelize.query('SELECT 1');
}, 60000); // 每分钟执行一次
16. 最佳实践
使用迁移而不是 sync():在生产环境中始终使用迁移来管理数据库结构变更
合理使用事务:对于需要原子性操作的多个数据库操作使用事务
验证输入数据:在模型层和业务逻辑层都进行数据验证
处理错误:妥善处理数据库错误,提供有意义的错误信息
日志记录:记录重要的数据库操作,但避免记录敏感信息
索引优化:根据查询模式合理设计索引
定期备份:即使有迁移脚本,也要定期备份数据库
17. 高级特性
多数据库连接
const sequelize1 = new Sequelize('database1', 'user', 'pass', { dialect: 'mysql' });
const sequelize2 = new Sequelize('database2', 'user', 'pass', { dialect: 'postgres' });
// 模型可以绑定到不同的 Sequelize 实例
const User = sequelize1.define('User', { /* ... */ });
const Product = sequelize2.define('Product', { /* ... */ });
读写分离
const sequelize = new Sequelize('database', null, null, {
dialect: 'mysql',
replication: {
read: [
{ host: 'read1.example.com', username: 'readuser', password: 'readpass' },
{ host: 'read2.example.com', username: 'readuser', password: 'readpass' }
],
write: { host: 'write.example.com', username: 'writeuser', password: 'writepass' }
},
pool: { // 为读写分离配置不同的连接池
max: 20,
idle: 30000
}
});
乐观锁
const User = sequelize.define('User', {
/* 属性定义 */
}, {
version: true // 启用乐观锁,添加 version 字段
});
// 更新时会自动检查 version
const user = await User.findByPk(1);
try {
user.name = 'New Name';
await user.save(); // 如果 version 不匹配会抛出 OptimisticLockError
} catch (error) {
if (error instanceof OptimisticLockError) {
// 处理并发冲突
}
}
18. 调试技巧
启用 SQL 日志:
const sequelize = new Sequelize(/* ... */, {
logging: console.log // 或自定义日志函数
});
使用 sequelize-logger:
npm install sequelize-loggerconst Sequelize = require('sequelize');
require('sequelize-logger')(Sequelize); // 在创建实例前调用
性能分析:
const { QueryTypes } = require('sequelize');
const start = Date.now();
const users = await User.findAll();
console.log(`Query took ${Date.now() - start}ms`);
19. 与 Express 集成示例
const express = require('express');
const { Sequelize, Op } = require('sequelize');
const app = express();
// 中间件
app.use(express.json());
// 路由
app.get('/users', async (req, res) => {
try {
const users = await User.findAll({
where: {
age: {
[Op.gte]: req.query.minAge || 0
}
},
limit: 100
});
res.json(users);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/users', async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (error) {
console.error(error);
res.status(400).json({ error: 'Bad request' });
}
});
// 错误处理
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
try {
await sequelize.authenticate();
console.log('Database connection has been established successfully.');
console.log(`Server is running on port ${PORT}`);
} catch (error) {
console.error('Unable to connect to the database:', error);
process.exit(1);
}
});
20. 资源与进一步学习
- 官方文档
- Sequelize GitHub
- Sequelize Cookbook
- Sequelize 中文文档
希望这份详细的 Sequelize 使用笔记对你的开发工作有所帮助!根据实际项目需求,你可以选择适合的功能和配置来优化你的数据库操作。