Sequelize — 老牌 Node ORM
已复核Sequelize 是 Node 上的 Active Record ORM:一个 Model class 同时描述表结构、提供查询入口,并在实例上挂 save() / destroy()。日常类比:User 既是户口本格式,也是窗口柜员——User.findAll() 去查,user.save() 把眼前这份记录写回去。
import { Sequelize, DataTypes } from "sequelize"const sequelize = new Sequelize("postgres://localhost/app")const User = sequelize.define("User", { email: { type: DataTypes.STRING, unique: true },})define(name, attrs) 的实现是:现场 class extends Model {},再 model.init(attributes, options)。class-based User.init(...) 与 define() 最终走同一条 Model.init。
不按固定 v6 源码读 Sequelize,下面这些事会对不上:
- 为什么老 Express 项目里满是
sequelize.define,而较新代码改写成class User extends Model - 为什么默认会多出
createdAt/updatedAt,表名又常常被复数化 - 为什么
transaction(async t => ...)成功就 commit、抛错就 rollback,而只调transaction()时要自己收尾 - 为什么字符串运算符别名已经被 deprecation 路径拦住,查询要写
Op.like
固定 6.37.8 可以看成四层:
-
构造期选定 dialect:
new Sequelize(...)用 switch 静态requiredialect,避免打包器动态路径。实现支持mariadb/mssql/mysql/oracle/postgres/sqlite/db2/snowflake。默认错误文案漏写了snowflake,但 case 存在。 -
Model.init 填默认选项:
timestamps: true、freezeTableName: false、paranoid: false、underscored: false、whereMergeStrategy: 'overwrite'。未给tableName时,表名是freezeTableName ? name : underscoredIf(pluralize(name))。 -
查询与关联挂在 Model 上:
findAll/create是 static;hasMany/belongsTo/hasOne/belongsToMany生成 getter。条件用Op.*symbol,不是字符串'$like'。 -
Hooks 与 transaction 是副作用边界:hooks 覆盖 validate/create/update/destroy/save/upsert/bulk/find/count/sync/query/connect 等阶段;
beforeSave会代理到beforeUpdate+beforeCreate。sequelize.transaction(fn)用 CLS 跑回调并自动 commit/rollback;不传回调则返回未结束的Transaction。
案例 1:define 一张表
Section titled “案例 1:define 一张表”import { Sequelize, DataTypes, Model } from "sequelize"
const sequelize = new Sequelize("sqlite::memory:")
const User = sequelize.define("User", { name: { type: DataTypes.STRING, allowNull: false }, email: { type: DataTypes.STRING, unique: true },})
class Post extends Model {}Post.init( { title: DataTypes.STRING }, { sequelize, modelName: "Post" },)两种写法都要求 Sequelize 实例进入 init;缺 options.sequelize 会直接抛 No Sequelize instance passed。默认 timestamps: true,所以同步后的表通常还有 createdAt / updatedAt。
案例 2:查询、关联与运算符
Section titled “案例 2:查询、关联与运算符”import { Op } from "sequelize"
User.hasMany(Post)Post.belongsTo(User)
const hits = await User.findAll({ where: { email: { [Op.like]: "%@example.com" } }, order: "id DESC", limit: 10, include: [Post],})Op.like 是 symbol。把 operatorsAliases 设成对象或布尔,固定源码会走 deprecation 辅助函数,不应再当新代码默认。
案例 3:回调式 transaction
Section titled “案例 3:回调式 transaction”await sequelize.transaction(async (t) => { const user = await User.create({ name: "Ada", email: "ada@example.com" }, { transaction: t }) await Post.create({ title: "Notes", UserId: user.id }, { transaction: t })})回调 resolve 后 commit(),reject 后尝试 rollback()。只调用 await sequelize.transaction() 时,调用方必须自己 commit / rollback;未传回调不会自动收尾。
- 以为
define和 classinit是两套 ORM:define只是造匿名 subclass 再init。 - 忘了默认 timestamps / 复数表名:
User默认表名是复数;freezeTableName: true才钉死类名。 - 字符串运算符:
where: { email: { $like: '...' } }依赖已废弃的 alias;固定 v6 要Op.like。 - 把
@sequelize/corev7 alpha 当成 npmsequelizelatest:npm view sequelize当前 latest 仍是6.37.8;v7 是另一包且为7.0.0-alpha.48,不能把 alpha 合同写进本页。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 维护已有 Express / 早期 Nest 的 Sequelize v6 代码
- 需要 Active Record 实例方法,并接受运行时 schema
- dialect 落在上述 8 个 switch case 内,且能接受各 dialect 深浅不一
不适用:
- 新项目若要以编译期 schema 为唯一真相——对照 prisma / drizzle / typeorm 的固定源码,不要外推“谁更新”
- 需要 Identity Map / Unit of Work——那是 mikro-orm 的合同,不是 Sequelize v6
- 想把 v7 alpha 行为写进生产结论
- 需要本文未做的真实 dialect 联调或性能数字
固定版本边界
Section titled “固定版本边界”- 本文绑定
sequelize/sequelize@cb7f99ad05de56137672ab95586359ff6ceba004。lightweight tagv6.37.8与 npmsequelize@6.37.8的gitHead一致。 - 该提交根
package.json的version是0.0.0-development(发布流程占位);对外版本以 npm6.37.8与 tag 名为准。engines.node为>=10.0.0。 @sequelize/core@7.0.0-alpha.48不在本页绑定范围。- 本文未安装依赖、未连库、未跑上游测试或
sync(),状态保持UNVERIFIED。
- Active Record 把 schema、查询、实例生命周期叠在同一个 class 上——好读,也难单测。
- 默认选项比文档印象更具体:timestamps、复数表名、
whereMergeStrategy: overwrite都在Model.init。 - transaction 的两种调用约定必须分开记:有回调才自动收尾。
- 多 dialect 的“支持”以 switch 为准,错误文案和实现列表可以不一致。
sequelize.define("User", attrs)和class User extends Model; User.init(attrs, { sequelize })是否走不同的持久化引擎?- 不设
timestamps时,User默认真的没有时间列吗? await sequelize.transaction()不传回调,抛错后会自动 rollback 吗?
检查点:
- 不是。
define创建 subclass 后调用同一套Model.init。 - 不是。
Model.init默认timestamps: true。 - 不会。无回调只返回
Transaction,收尾由调用方负责。
- 官方文档:sequelize.org
- 固定源码:sequelize/sequelize —— 本文绑定提交
cb7f99ad05de56137672ab95586359ff6ceba004 - typeorm —— 装饰器 metadata + DataSource,对照 Active Record
- mikro-orm —— Data Mapper + Identity Map
- prisma —— schema-first 生成客户端
- typeorm —— 装饰器 entity / DataSource vs
define/Model.init - mikro-orm ——
em.flush()对照 Sequelize 实例save() - prisma —— 生成类型与 migration 工作流不同
- drizzle —— SQL-like schema-as-code
- postgresql —— 常见 dialect 之一;各 dialect 行为以对应 driver 为准
- codd-1979-extending —— Codd 1979 — 给关系模型补上”语义”
- mikro-orm —— MikroORM — Data Mapper Identity Map ORM