Sequelize 在 Node.js 中的详细用法与使用笔记

Sequelize 在 Node.js 中的详细用法与使用笔记

编程文章jaq1232025-06-29 21:53:574A+A-

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. 性能优化技巧

批量操作:使用 bulkCreateupdatedestroy 进行批量操作

选择性查询:只查询需要的字段

User.findAll({
attributes: ['id', 'firstName']
});

分页:使用 limitoffset 实现分页

预加载关联数据:使用 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 使用笔记对你的开发工作有所帮助!根据实际项目需求,你可以选择适合的功能和配置来优化你的数据库操作。

点击这里复制本文地址 以上内容由jaq123整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

苍茫编程网 © All Rights Reserved.  蜀ICP备2024111239号-21