Sequelize入门

892 阅读8分钟

资源:

Sequelize 中文文档 sequelize API sequelize 小技巧 sequelize 菜鸟教程

核心概念:

连接数据库:

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

// 方法 1: 传递一个连接 URI
const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Postgres 示例

// 方法 2: 分别传递参数 (sqlite)
const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'path/to/database.sqlite'
});

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

模型定义:

sequelize.define('model_name',{filed:value})

创建表:

首先定义模型: model 然后同步:model.sync() 创建表会自动创建主键,默认为 id

增删改查:

  • 新增数据:model.create 相当于 build save两步合并;
  • 批量新增:model.bulkCreate([model,...],{...}) ; 但是默认不会运行验证器,需要手动开启
 User.bulkCreate([
  { username: 'foo' },
  { username: 'bar', admin: true }
], { validate: true,//手动开启验证器
fields: ['username']//限制字段 
});
// 因为限制了字段只存username,foo 和 bar 都不会是管理员.
  • 更新 model.update 相当于 set, save两步合并,通常就直接修改实例属性,然后save()更新;
  • 部分更新: 通过传递一个列名数组,可以定义在调用 save 时应该保存哪些属性 save({fields:[ 'name',... ]}) 只更新数组里面的字段
  • 删除 model.destroy
  • 重载实例:model.reload
  • 查询
    include参数 对应sql的 join连接操作 findAll 查找所有的 findByPk 根据主键查找 findOne 找到第一个实例 findOrCreate 查找到或创建实例 findAndCountAll 分页查找
查询的选项参数
Model.findAll({
//查询指定字段
  attributes: ['foo', 'bar',
  [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats']//函数聚合
]  ,
 where: {//对应where子句,过滤
    authorId: 2,
   authorId: {
      [Sequelize.Op.eq]: 2  //操作符运算
    }
  },
order:[], //排序
group:'name',//分组
limit:10,//限制
offset:1//页
});

实用方法:

count,max, min 和 sum

原始查询:

const { QueryTypes } = require('sequelize');
const users = await sequelize.query("SELECT * FROM `users`");//参数是sql语句

偏执表:

Sequelize 支持 paranoid 表的概念 这意味着删除记录时不会真的删除,而是给字段deletedAt值设置为时间戳 删除的时候默认是软删除,而不是硬删除

class Post extends Model {}
Post.init({ /* 这是属性 */ }, {
  sequelize,
  paranoid: true,// 传递该参数,创建偏执表
  // 如果要为 deletedAt 列指定自定义名称
  deletedAt: 'destroyTime'
});

强制删除:

await Post.destroy({
  where: {
    id: 1
  },
  force: true //硬删除
});

软删除的实例,恢复:

post.restore();

Post.restore({
  where: {
    likes: {
      [Op.gt]: 100
    }
  }
});

查询包含软删除的记录:

await Post.findAll({
  where: { foo: 'bar' },
  paranoid: false
});

关联类型:

对应 sql语句的 foreign key 进行表关联 HasOne BelongsTo HasMany BelongsToMany

const A = sequelize.define('A', /* ... */);
const B = sequelize.define('B', /* ... */);

A.hasOne(B); // A 有一个 B ,外键在目标模型(B)中定义
A.belongsTo(B); // A 属于 B  ,外键在目标模型(A)中定义
A.hasMany(B); // A 有多个 B 外键在目标模型(B)中定义
A.belongsToMany(B, { through: 'C' }); // A 属于多个 B , 通过联结表 C

A.belongsToMany(B, { through: 'C' }) 关联意味着将表 C 用作联结表,在 A 和 B 之间存在多对多关系. 具有外键(例如,aId 和 bId). Sequelize 将自动创建此模型 C(除非已经存在),并在其上定义适当的外键.

创建标准关系:
  • 创建一个 一对一 关系, hasOne 和 belongsTo 关联一起使用;
  • 创建一个 一对多 关系, hasMany he belongsTo 关联一起使用;
  • 创建一个 多对多 关系, 两个 belongsToMany 调用一起使用.

添加到实例的特殊方法:

创建关联关系后,这些模型的实例会获得特殊方法 例如:有两个模型 FooBar 拥有关联关系,则根据关联类型拥有以下可用方法;

Foo.hasOne(Bar) 和 Foo.belongsTo(Bar)#
  • fooInstance.getBar()
  • fooInstance.setBar()
  • fooInstance.createBar()
Foo.hasMany(Bar) 和 Foo.belongsToMany(Bar, { through: Baz })#
  • fooInstance.getBars()
  • fooInstance.countBars()
  • fooInstance.hasBar()
  • fooInstance.hasBars()
  • fooInstance.setBars()
  • fooInstance.addBar()
  • fooInstance.addBars()
  • fooInstance.removeBar()
  • fooInstance.removeBars()
  • fooInstance.createBar()

多对多关系:

代码分析:

  1. 创建表Foo,Bar ,设置为多对多,中间表为Foo_Bar
  2. sequelize.sync();同步到数据库,就是说如果模型对应的表不存在就创建 插入数据 foo, bar
  3. foo.addBar(bar) ,foo 关联了一个bar,反映到数据库上面,则是中间表Foo_Bar插入一条数据 INSERT INTO Foo_Bar (FooId,BarId) VALUES(1,1)
  4. Foo.findOne({ include: Bar });数据查询,根据模型,查出Foo表的第一条数据, 并带上关联表数据,字段是Bars(因为是多对多,所以这里是复数形式,每一条bar包含中间表的数据字段是 Foo_Bar
const Foo = sequelize.define('Foo', { name: DataTypes.TEXT });
const Bar = sequelize.define('Bar', { name: DataTypes.TEXT });
Foo.belongsToMany(Bar, { through: 'Foo_Bar' });
Bar.belongsToMany(Foo, { through: 'Foo_Bar' });

await sequelize.sync();
const foo = await Foo.create({ name: 'foo' });
const bar = await Bar.create({ name: 'bar' });
await foo.addBar(bar);// foo这条数据关联了一条bar,反映到表上则是在中间表Foo_Bar上插入一条数据
const fetchedFoo =await  Foo.findOne({ include: Bar });
console.log(JSON.stringify(fetchedFoo, null, 2));

输出:

{
  "id": 1,
  "name": "foo",
  "Bars": [
    {
      "id": 1,
      "name": "bar",
      "Foo_Bar": {
        "FooId": 1,
        "BarId": 1
      }
    }
  ]
}

高级关联概念

预先加载:

查询方法中使用 include 参数完成预先加载,翻译成sql其实就是 通过join关联子句;

创建关联:

可以一次性创建带关联关系的数据

高级M:N关联:

超级多对多:

超级多对多创建出来的表跟多对多一样,没什么区别,区别就是一次使用6个关联,然后就可以进行各种预先加载

//模型:
const User = sequelize.define('user', {
  username: DataTypes.STRING,
  points: DataTypes.INTEGER
}, { timestamps: false });

const Profile = sequelize.define('profile', {
  name: DataTypes.STRING
}, { timestamps: false });
//自定义中间表
const Grant = sequelize.define('grant', {
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
    allowNull: false
  },
  selfGranted: DataTypes.BOOLEAN
}, { timestamps: false });

// 超级多对多关系
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
User.hasMany(Grant);
Grant.belongsTo(User);
Profile.hasMany(Grant);
Grant.belongsTo(Profile);

这样,我们可以进行各种预先加载:

// 全部可以使用:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });

多态关联:

多态关联,就是说一个联结表的外键关联多个表 由于外键引用了多个表,无法添加REFERENCES约束,需要禁用约束 constraints: false

一对多的多态关联:

考虑模型 Image Video Comment , 图片,视频都可以有多个评论, 但是一个评论只能是图片跟视频中的其中一种类型;

// Helper 方法
const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}`;

class Image extends Model {}
Image.init({
  title: DataTypes.STRING,
  url: DataTypes.STRING
}, { sequelize, modelName: 'image' });

class Video extends Model {}
Video.init({
  title: DataTypes.STRING,
  text: DataTypes.STRING
}, { sequelize, modelName: 'video' });

class Comment extends Model {
  getCommentable(options) {//获取评论关联类型的那个实例
    if (!this.commentableType) return Promise.resolve(null);
    const mixinMethodName = `get${uppercaseFirst(this.commentableType)}`;
    return this[mixinMethodName](options);
  }
}
Comment.init({
  title: DataTypes.STRING,
  commentableId: DataTypes.INTEGER,
  commentableType: DataTypes.STRING
}, { sequelize, modelName: 'comment' });

Image.hasMany(Comment, {
  foreignKey: 'commentableId',
  constraints: false,
  scope: {//关联作用域  commentableType = 'image'
    commentableType: 'image'
  }
});
Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false });

Video.hasMany(Comment, {
  foreignKey: 'commentableId',
  constraints: false,
  scope: {//关联作用域  commentableType = 'video'
    commentableType: 'video'
  }
});
Comment.belongsTo(Video, { foreignKey: 'commentableId', constraints: false });

Comment.addHook("afterFind", findResult => {
  console.log('afterFind,findResult=====',findResult);
  if (!Array.isArray(findResult)) findResult = [findResult];
  for (const instance of findResult) {
    if (instance.commentableType === "image" && instance.image !== undefined) {
      instance.commentable = instance.image;
    } else if (instance.commentableType === "video" && instance.video !== undefined) {
      instance.commentable = instance.video;
    }
    // 防止错误:
     delete instance.image;
     delete instance.dataValues.image;
     delete instance.video;
     delete instance.dataValues.video;
  }
});
多对多多态关联:
class Tag extends Model {
  getTaggables(options) {
    const images = await this.getImages(options);
    const videos = await this.getVideos(options);
    // 在单个 taggables 数组中合并 images 和 videos
    return images.concat(videos);
  }
}
Tag.init({
  name: DataTypes.STRING
}, { sequelize, modelName: 'tag' });

// 在这里,我们明确定义联结模型
class Tag_Taggable extends Model {}
Tag_Taggable.init({
  tagId: {
    type: DataTypes.INTEGER,
    unique: 'tt_unique_constraint'
  },
  taggableId: {
    type: DataTypes.INTEGER,
    unique: 'tt_unique_constraint',
    references: null
  },
  taggableType: {
    type: DataTypes.STRING,
    unique: 'tt_unique_constraint'
  }
}, { sequelize, modelName: 'tag_taggable' });

Image.belongsToMany(Tag, {
  through: {
    model: Tag_Taggable,
    unique: false,
    scope: {//注意这里的作用域用于关联模型,因为这scope参数在through下
      taggableType: 'image'
    }
  },
  foreignKey: 'taggableId',
  constraints: false
});
Tag.belongsToMany(Image, {
  through: {
    model: Tag_Taggable,
    unique: false
  },
  foreignKey: 'tagId',
  constraints: false
});

Video.belongsToMany(Tag, {
  through: {
    model: Tag_Taggable,
    unique: false,
    scope: {//注意这里的作用域用于关联模型,因为这scope参数在through下
      taggableType: 'video'
    }
  },
  foreignKey: 'taggableId',
  constraints: false
});
Tag.belongsToMany(Video, {
  through: {
    model: Tag_Taggable,
    unique: false
  },
  foreignKey: 'tagId',
  constraints: false
});

###### 在目标模型上应用作用域 我们还可以在目标模型上应用关联作用域. 我们甚至可以同时进行,以下实例:

Image.belongsToMany(Tag, {
  through: {
    model: Tag_Taggable,
    unique: false,
    scope: {
      taggableType: 'image'
    }
  },
  scope: {
    status: 'pending'
  },
  as: 'pendingTags',
  foreignKey: 'taggableId',
  constraints: false
});

### 其他主题

事务:

Sequelize 支持两种使用事务的方式:

  1. 非托管事务: 提交和回滚事务应由用户手动完成(通过调用适当的 Sequelize 方法).
  2. 托管事务: 如果引发任何错误,Sequelize 将自动回滚事务,否则将提交事务. 另外,如果启用了CLS(连续本地存储),则事务回调中的所有查询将自动接收事务对象.
非托管事务:
// 首先,我们开始一个事务并将其保存到变量中
const t = await sequelize.transaction();

try {

  // 然后,我们进行一些调用以将此事务作为参数传递:

  const user = await User.create({
    firstName: 'Bart',
    lastName: 'Simpson'
  }, { transaction: t });

  await user.addSibling({
    firstName: 'Lisa',
    lastName: 'Simpson'
  }, { transaction: t });

  // 如果执行到此行,且没有引发任何错误.
  // 我们手动提交事务.
  await t.commit();

} catch (error) {

  // 如果执行到达此行,则抛出错误.
  // 我们回滚事务.
  await t.rollback();

}
托管事务:
try {

  const result = await sequelize.transaction(async (t) => {

    const user = await User.create({
      firstName: 'Abraham',
      lastName: 'Lincoln'
    }, { transaction: t });

    await user.setShooter({
      firstName: 'John',
      lastName: 'Boothe'
    }, { transaction: t });

    return user;

  });

  // 如果执行到此行,则表示事务已成功提交,`result`是事务返回的结果
  // `result` 就是从事务回调中返回的结果(在这种情况下为 `user`)

} catch (error) {

  // 如果执行到此,则发生错误.
  // 该事务已由 Sequelize 自动回滚!

}

作用域:

不同于关联作用域, 作用域定义在模型中,帮助重用代码 作用域在模型定义中定义,可以是查找器对象,也可以是返回查找器对象的函数 - 默认作用域除外,该作用域只能是一个对象

class Project extends Model {}
Project.init({
  // 属性
}, {
  defaultScope: {//默认作用域
    where: {
      active: true
    }
  },
  scopes: {
    deleted: {
      where: {
        deleted: true
      }
    },
    activeUsers: {
      include: [
        { model: User, where: { active: true } }
      ]
    },
    random() {
      return {
        where: {
          someNumber: Math.random()
        }
      }
    }
},
    sequelize,
    modelName: 'project'
  
});
await Project.scope('deleted').findAll(); //用法就是调用scope方法传入字符串,返回一个查询对象
SELECT * FROM projects WHERE deleted = true // sql
    await Project.scope('random', { method: ['accessLevel', 19] }).findAll();
SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19// sql