Sequelize - Node.js ORM框架

1. 什么是Sequelize?

Sequelize是一个基于Promise的Node.js ORM(对象关系映射)框架,支持PostgreSQL、MySQL、MariaDB、SQLite和Microsoft SQL Server等多种关系型数据库。它提供了丰富的功能,包括模型定义、关联关系、事务、迁移、种子数据等,使得数据库操作更加直观和高效。

1.1 核心特性

  • 支持多种数据库:PostgreSQL、MySQL、MariaDB、SQLite、Microsoft SQL Server
  • 基于Promise的API:支持async/await语法
  • 自动数据库迁移:管理数据库架构变更
  • 模型关联:支持一对一、一对多、多对多关系
  • 事务支持:确保数据一致性
  • 数据验证:内置验证规则
  • 钩子函数:支持模型生命周期事件
  • 查询构建器:灵活的查询API

2. 快速开始

2.1 安装

# 安装Sequelize核心包
npm install sequelize

# 安装相应数据库的驱动
# 例如,MySQL
npm install mysql2
# 或PostgreSQL
npm install pg pg-hstore
# 或SQLite
npm install sqlite3
# 或Microsoft SQL Server
npm install tedious

2.2 基本配置

const { Sequelize } = require('sequelize');

// 方法1: 单独传递参数
const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql' /* 选择 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */
});

// 方法2: 使用连接URL
const sequelize = new Sequelize('mysql://username:password@localhost:3306/database');

// 测试连接
async function testConnection() {
  try {
    await sequelize.authenticate();
    console.log('连接成功');
  } catch (error) {
    console.error('连接失败:', error);
  }
}

testConnection();

3. 模型定义

3.1 基本模型

const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('mysql://root:password@localhost:3306/test');

// 定义模型
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,
    defaultValue: 18
  },
  createdAt: {
    type: DataTypes.DATE,
    defaultValue: Sequelize.NOW
  }
}, {
  // 模型选项
  tableName: 'users', // 自定义表名
  timestamps: true // 自动添加createdAt和updatedAt字段
});

// 同步模型到数据库
async function syncModel() {
  await User.sync({ force: false }); // force: true会删除现有表并重新创建
  console.log('User模型已同步');
}

syncModel();

3.2 数据类型

Sequelize支持多种数据类型:

  • 字符串类型:STRING, TEXT, TINYTEXT, MEDIUMTEXT, LONGTEXT
  • 数字类型:INTEGER, BIGINT, FLOAT, DOUBLE, DECIMAL
  • 布尔类型:BOOLEAN
  • 日期类型:DATE, DATEONLY
  • 二进制类型:BLOB, TINYBLOB, MEDIUMBLOB, LONGBLOB
  • 枚举类型:ENUM
  • 数组类型:ARRAY (仅PostgreSQL支持)
  • JSON类型:JSON, JSONB (仅PostgreSQL支持)

4. CRUD操作

4.1 创建记录

// 方法1: 使用create()
async function createUser() {
  try {
    const user = await User.create({
      name: '张三',
      email: 'zhangsan@example.com',
      age: 30
    });
    console.log('创建的用户:', user.toJSON());
  } catch (error) {
    console.error('创建用户失败:', error);
  }
}

// 方法2: 先构建实例,再保存
async function buildAndSaveUser() {
  const user = User.build({
    name: '李四',
    email: 'lisi@example.com',
    age: 25
  });
  await user.save();
  console.log('保存的用户:', user.toJSON());
}

createUser();
buildAndSaveUser();

4.2 查询记录

// 查找所有记录
async function findAllUsers() {
  const users = await User.findAll();
  console.log('所有用户:', users.map(user => user.toJSON()));
}

// 按条件查找
async function findUsersByAge() {
  const users = await User.findAll({
    where: {
      age: {
        [Sequelize.Op.gt]: 25 // 年龄大于25
      }
    }
  });
  console.log('年龄大于25的用户:', users.map(user => user.toJSON()));
}

// 查找单个记录
async function findOneUser() {
  const user = await User.findOne({
    where: {
      email: 'zhangsan@example.com'
    }
  });
  console.log('找到的用户:', user ? user.toJSON() : '未找到');
}

// 按主键查找
async function findUserById() {
  const user = await User.findByPk(1);
  console.log('按ID找到的用户:', user ? user.toJSON() : '未找到');
}

findAllUsers();
findUsersByAge();
findOneUser();
findUserById();

4.3 更新记录

// 方法1: 更新单个实例
async function updateUser() {
  const user = await User.findByPk(1);
  if (user) {
    user.name = '张三更新';
    user.age = 31;
    await user.save();
    console.log('更新后的用户:', user.toJSON());
  }
}

// 方法2: 批量更新
async function updateMultipleUsers() {
  const result = await User.update(
    { age: 20 },
    {
      where: {
        age: {
          [Sequelize.Op.lt]: 18
        }
      }
    }
  );
  console.log('更新的行数:', result[0]);
}

updateUser();
updateMultipleUsers();

4.4 删除记录

// 方法1: 删除单个实例
async function deleteUser() {
  const user = await User.findByPk(1);
  if (user) {
    await user.destroy();
    console.log('用户已删除');
  }
}

// 方法2: 批量删除
async function deleteMultipleUsers() {
  const result = await User.destroy({
    where: {
      age: {
        [Sequelize.Op.gt]: 60
      }
    }
  });
  console.log('删除的行数:', result);
}

deleteUser();
deleteMultipleUsers();

5. 模型关联

5.1 一对一关系

// 定义Profile模型
const Profile = sequelize.define('Profile', {
  bio: DataTypes.TEXT,
  avatar: DataTypes.STRING
});

// 定义User和Profile的一对一关系
User.hasOne(Profile, {
  foreignKey: 'userId',
  as: 'profile'
});
Profile.belongsTo(User, {
  foreignKey: 'userId',
  as: 'user'
});

// 创建带关联的用户
async function createUserWithProfile() {
  const user = await User.create({
    name: '王五',
    email: 'wangwu@example.com',
    profile: {
      bio: '这是个人简介',
      avatar: 'avatar.jpg'
    }
  }, {
    include: 'profile'
  });
  console.log('创建的用户及简介:', user.toJSON());
}

// 查询带关联的用户
async function findUserWithProfile() {
  const user = await User.findByPk(1, {
    include: 'profile'
  });
  console.log('用户及简介:', user.toJSON());
}

createUserWithProfile();
findUserWithProfile();

5.2 一对多关系

// 定义Post模型
const Post = sequelize.define('Post', {
  title: DataTypes.STRING,
  content: DataTypes.TEXT
});

// 定义User和Post的一对多关系
User.hasMany(Post, {
  foreignKey: 'userId',
  as: 'posts'
});
Post.belongsTo(User, {
  foreignKey: 'userId',
  as: 'user'
});

// 创建带关联的用户和帖子
async function createUserWithPosts() {
  const user = await User.create({
    name: '赵六',
    email: 'zhaoliu@example.com',
    posts: [
      {
        title: '第一篇帖子',
        content: '帖子内容1'
      },
      {
        title: '第二篇帖子',
        content: '帖子内容2'
      }
    ]
  }, {
    include: 'posts'
  });
  console.log('创建的用户及帖子:', user.toJSON());
}

// 查询带关联的用户和帖子
async function findUserWithPosts() {
  const user = await User.findByPk(1, {
    include: 'posts'
  });
  console.log('用户及帖子:', user.toJSON());
}

createUserWithPosts();
findUserWithPosts();

5.3 多对多关系

// 定义Tag模型
const Tag = sequelize.define('Tag', {
  name: DataTypes.STRING
});

// 定义Post和Tag的多对多关系(通过中间表)
const PostTag = sequelize.define('PostTag', {}, {
  timestamps: false
});

Post.belongsToMany(Tag, {
  through: PostTag,
  as: 'tags',
  foreignKey: 'postId'
});
Tag.belongsToMany(Post, {
  through: PostTag,
  as: 'posts',
  foreignKey: 'tagId'
});

// 创建带标签的帖子
async function createPostWithTags() {
  const post = await Post.create({
    title: '带标签的帖子',
    content: '帖子内容',
    tags: [
      { name: '技术' },
      { name: '教程' }
    ]
  }, {
    include: 'tags'
  });
  console.log('创建的帖子及标签:', post.toJSON());
}

// 查询带标签的帖子
async function findPostWithTags() {
  const post = await Post.findByPk(1, {
    include: 'tags'
  });
  console.log('帖子及标签:', post.toJSON());
}

createPostWithTags();
findPostWithTags();

6. 事务

6.1 基本事务

async function transferMoney() {
  const t = await sequelize.transaction();
  
  try {
    // 从用户1扣款
    const user1 = await User.findByPk(1, { transaction: t });
    user1.balance -= 100;
    await user1.save({ transaction: t });
    
    // 给用户2加款
    const user2 = await User.findByPk(2, { transaction: t });
    user2.balance += 100;
    await user2.save({ transaction: t });
    
    // 提交事务
    await t.commit();
    console.log('转账成功');
  } catch (error) {
    // 回滚事务
    await t.rollback();
    console.error('转账失败:', error);
  }
}

transferMoney();

6.2 自动事务(使用回调)

async function autoTransaction() {
  await sequelize.transaction(async (t) => {
    // 所有操作都在事务中执行
    const user1 = await User.findByPk(1, { transaction: t });
    user1.balance -= 50;
    await user1.save({ transaction: t });
    
    const user2 = await User.findByPk(2, { transaction: t });
    user2.balance += 50;
    await user2.save({ transaction: t });
  });
  console.log('自动事务执行成功');
}

autoTransaction();

7. 迁移

7.1 安装迁移工具

npm install --save-dev sequelize-cli

7.2 初始化迁移配置

npx sequelize-cli init

这会创建以下目录结构:

  • config: 数据库配置文件
  • models: 模型文件
  • migrations: 迁移文件
  • seeders: 种子文件

7.3 创建迁移文件

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

7.4 运行迁移

# 运行所有待执行的迁移
npx sequelize-cli db:migrate

# 回滚上一次迁移
npx sequelize-cli db:migrate:undo

# 回滚所有迁移
npx sequelize-cli db:migrate:undo:all

7.5 示例迁移文件

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      name: {
        type: Sequelize.STRING
      },
      email: {
        type: Sequelize.STRING,
        unique: true
      },
      age: {
        type: Sequelize.INTEGER
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Users');
  }
};

8. 种子数据

8.1 创建种子文件

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

8.2 运行种子

# 运行所有种子
npx sequelize-cli db:seed:all

# 回滚上一次种子
npx sequelize-cli db:seed:undo

# 回滚所有种子
npx sequelize-cli db:seed:undo:all

8.3 示例种子文件

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.bulkInsert('Users', [
      {
        name: '管理员',
        email: 'admin@example.com',
        age: 30,
        createdAt: new Date(),
        updatedAt: new Date()
      },
      {
        name: '测试用户',
        email: 'test@example.com',
        age: 25,
        createdAt: new Date(),
        updatedAt: new Date()
      }
    ], {});
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete('Users', null, {});
  }
};

9. 钩子函数

9.1 模型钩子

// 在模型定义中添加钩子
const User = sequelize.define('User', {
  // 属性定义
}, {
  hooks: {
    // 创建前
    beforeCreate: (user) => {
      console.log('创建用户前:', user.name);
    },
    // 创建后
    afterCreate: (user) => {
      console.log('创建用户后:', user.id);
    },
    // 更新前
    beforeUpdate: (user) => {
      console.log('更新用户前:', user.id);
    },
    // 更新后
    afterUpdate: (user) => {
      console.log('更新用户后:', user.id);
    },
    // 删除前
    beforeDestroy: (user) => {
      console.log('删除用户前:', user.id);
    },
    // 删除后
    afterDestroy: (user) => {
      console.log('删除用户后:', user.id);
    }
  }
});

9.2 实例钩子

// 为实例添加钩子
User.prototype.beforeSave = function() {
  console.log('保存实例前:', this.name);
};

10. 数据验证

10.1 内置验证器

const User = sequelize.define('User', {
  name: {
    type: DataTypes.STRING,
    allowNull: false,
    validate: {
      notEmpty: true,
      len: [2, 50] // 长度在2-50之间
    }
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true, // 邮箱格式验证
      notEmpty: true
    }
  },
  age: {
    type: DataTypes.INTEGER,
    validate: {
      isInt: true, // 整数验证
      min: 0, // 最小值
      max: 120 // 最大值
    }
  },
  password: {
    type: DataTypes.STRING,
    validate: {
      len: [6, Infinity], // 最小长度6
      is: /^[a-zA-Z0-9_]+$/ // 自定义正则验证
    }
  }
});

10.2 自定义验证器

const User = sequelize.define('User', {
  username: {
    type: DataTypes.STRING,
    validate: {
      // 自定义验证函数
      isUnique: async function(value) {
        const user = await User.findOne({ where: { username: value } });
        if (user) {
          throw new Error('用户名已存在');
        }
      },
      // 带参数的自定义验证
      customValidator: function(value) {
        if (value === 'admin') {
          throw new Error('用户名不能为admin');
        }
      }
    }
  }
});

11. 高级查询

11.1 聚合查询

// 计数
async function countUsers() {
  const count = await User.count();
  console.log('用户总数:', count);
}

// 按条件计数
async function countUsersByAge() {
  const count = await User.count({
    where: {
      age: {
        [Sequelize.Op.gt]: 25
      }
    }
  });
  console.log('年龄大于25的用户数:', count);
}

// 求和
async function sumAges() {
  const sum = await User.sum('age');
  console.log('年龄总和:', sum);
}

// 平均值
async function averageAge() {
  const avg = await User.average('age');
  console.log('平均年龄:', avg);
}

// 最大值
async function maxAge() {
  const max = await User.max('age');
  console.log('最大年龄:', max);
}

// 最小值
async function minAge() {
  const min = await User.min('age');
  console.log('最小年龄:', min);
}

countUsers();
countUsersByAge();
sumAges();
averageAge();
maxAge();
minAge();

11.2 分组查询

async function groupByAge() {
  const result = await User.findAll({
    attributes: [
      'age',
      [Sequelize.fn('COUNT', Sequelize.col('id')), 'userCount']
    ],
    group: ['age'],
    order: [[Sequelize.fn('COUNT', Sequelize.col('id')), 'DESC']]
  });
  console.log('按年龄分组:', result.map(item => item.toJSON()));
}

groupByAge();

11.3 子查询

async function subQuery() {
  const subQuery = await User.findAll({
    attributes: ['userId'],
    where: {
      age: {
        [Sequelize.Op.gt]: 30
      }
    },
    raw: true
  });
  
  const userIds = subQuery.map(item => item.userId);
  
  const posts = await Post.findAll({
    where: {
      userId: {
        [Sequelize.Op.in]: userIds
      }
    }
  });
  
  console.log('年龄大于30的用户的帖子:', posts.map(post => post.toJSON()));
}

subQuery();

12. 性能优化

12.1 索引

const User = sequelize.define('User', {
  // 属性定义
}, {
  indexes: [
    // 普通索引
    { fields: ['email'] },
    // 复合索引
    { fields: ['name', 'age'] },
    // 唯一索引
    { fields: ['username'], unique: true },
    // 部分索引 (仅PostgreSQL支持)
    { 
      fields: ['status'], 
      where: { status: 'active' }
    }
  ]
});

12.2 延迟加载

// 延迟加载关联数据
async function lazyLoad() {
  const user = await User.findByPk(1);
  // 当需要时才加载关联数据
  const posts = await user.getPosts();
  console.log('用户的帖子:', posts.map(post => post.toJSON()));
}

lazyLoad();

12.3 批量操作

// 批量创建
async function bulkCreate() {
  const users = await User.bulkCreate([
    { name: '用户1', email: 'user1@example.com' },
    { name: '用户2', email: 'user2@example.com' },
    { name: '用户3', email: 'user3@example.com' }
  ]);
  console.log('批量创建的用户数:', users.length);
}

// 批量更新
async function bulkUpdate() {
  await User.bulkCreate([
    { id: 1, name: '更新用户1' },
    { id: 2, name: '更新用户2' }
  ], {
    updateOnDuplicate: ['name'] // 当主键冲突时更新name字段
  });
  console.log('批量更新完成');
}

bulkCreate();
bulkUpdate();

13. 最佳实践

13.1 项目结构

src/
├── config/
│   └── database.js    # 数据库配置
├── models/
│   ├── index.js       # 模型索引
│   ├── user.js        # 用户模型
│   ├── post.js        # 帖子模型
│   └── tag.js         # 标签模型
├── migrations/        # 迁移文件
├── seeders/           # 种子文件
├── controllers/       # 控制器
├── routes/            # 路由
└── app.js             # 应用入口

13.2 模型索引文件

// models/index.js
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const config = require('../config/database');

const sequelize = new Sequelize(config.database, config.username, config.password, config);
const db = {};

fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

13.3 错误处理

// 全局错误处理中间件
app.use((err, req, res, next) => {
  // 验证错误
  if (err.name === 'SequelizeValidationError') {
    const errors = err.errors.map(error => error.message);
    return res.status(400).json({ errors });
  }
  
  // 唯一约束错误
  if (err.name === 'SequelizeUniqueConstraintError') {
    return res.status(400).json({ error: '该值已存在' });
  }
  
  // 外键约束错误
  if (err.name === 'SequelizeForeignKeyConstraintError') {
    return res.status(400).json({ error: '关联数据不存在' });
  }
  
  // 其他错误
  console.error(err);
  res.status(500).json({ error: '服务器内部错误' });
});

13.4 环境配置

// config/database.js
require('dotenv').config();

module.exports = {
  development: {
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    host: process.env.DB_HOST,
    dialect: process.env.DB_DIALECT
  },
  test: {
    username: process.env.TEST_DB_USERNAME,
    password: process.env.TEST_DB_PASSWORD,
    database: process.env.TEST_DB_NAME,
    host: process.env.TEST_DB_HOST,
    dialect: process.env.DB_DIALECT
  },
  production: {
    username: process.env.PROD_DB_USERNAME,
    password: process.env.PROD_DB_PASSWORD,
    database: process.env.PROD_DB_NAME,
    host: process.env.PROD_DB_HOST,
    dialect: process.env.DB_DIALECT,
    pool: {
      max: 5,
      min: 0,
      acquire: 30000,
      idle: 10000
    }
  }
};

14. 总结

Sequelize是一个功能强大的Node.js ORM框架,它大大简化了与关系型数据库的交互。通过本教程的学习,您应该已经掌握了以下核心内容:

14.1 核心优势

  • 简化数据库操作:通过面向对象的方式操作数据库
  • 跨数据库兼容:同一套代码可以在不同数据库间切换
  • 强大的关联支持:灵活处理复杂的数据关系
  • 完善的工具链:包括迁移、种子、CLI工具等
  • 现代JavaScript语法:支持Promise和async/await

14.2 适用场景

  • Web应用后端:处理用户数据、内容管理等
  • API服务:构建RESTful或GraphQL API
  • 企业应用:需要复杂数据关系和事务支持的场景
  • 数据迁移:管理数据库架构变更

14.3 最佳实践

  • 合理设计模型:遵循数据库设计原则
  • 使用迁移工具:管理数据库架构变更
  • 优化查询:合理使用索引和预加载
  • 错误处理:妥善处理数据库错误
  • 环境配置:区分开发、测试和生产环境

Sequelize的强大功能和灵活性使其成为Node.js生态系统中最受欢迎的ORM框架之一。通过本教程的学习,您应该已经具备了在实际项目中使用Sequelize的能力,可以开始构建更加健壮和可维护的数据库应用了。

« 上一篇 59. Mongoose - MongoDB对象建模工具 下一篇 » 61. Redis - 高性能内存数据库