Sequelize入門

資源:

Sequelize 中文文檔
sequelize API
sequelize 小技巧
sequelize 菜鳥教程

核心概念:

連接數(shù)據(jù)庫(kù):

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

// 方法 1: 傳遞一個(gè)連接 URI
const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Postgres 示例

// 方法 2: 分別傳遞參數(shù) (sqlite)
const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'path/to/database.sqlite'
});

// 方法 2: 分別傳遞參數(shù) (其它數(shù)據(jù)庫(kù))
const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: /* 選擇 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */
});

模型定義:

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

創(chuàng)建表:

首先定義模型: model
然后同步:model.sync()
創(chuàng)建表會(huì)自動(dòng)創(chuàng)建主鍵,默認(rèn)為 id

增刪改查:

  • 新增數(shù)據(jù):model.create 相當(dāng)于 build save兩步合并;
  • 批量新增:model.bulkCreate([model,...],{...}) ;
    但是默認(rèn)不會(huì)運(yùn)行驗(yàn)證器,需要手動(dòng)開啟
 User.bulkCreate([
  { username: 'foo' },
  { username: 'bar', admin: true }
], { validate: true,//手動(dòng)開啟驗(yàn)證器
fields: ['username']//限制字段 
});
// 因?yàn)橄拗屏俗侄沃淮鎢sername,foo 和 bar 都不會(huì)是管理員.
  • 更新 model.update
    相當(dāng)于 set, save兩步合并,通常就直接修改實(shí)例屬性,然后save()更新;
  • 部分更新:
    通過傳遞一個(gè)列名數(shù)組,可以定義在調(diào)用 save 時(shí)應(yīng)該保存哪些屬性
    save({fields:[ 'name',... ]}) 只更新數(shù)組里面的字段
  • 刪除 model.destroy
  • 重載實(shí)例:model.reload
  • 查詢
    include參數(shù) 對(duì)應(yīng)sql的 join連接操作
    findAll 查找所有的
    findByPk 根據(jù)主鍵查找
    findOne 找到第一個(gè)實(shí)例
    findOrCreate 查找到或創(chuàng)建實(shí)例
    findAndCountAll 分頁(yè)查找
查詢的選項(xiàng)參數(shù)
Model.findAll({
//查詢指定字段
  attributes: ['foo', 'bar',
  [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats']//函數(shù)聚合
]  ,
 where: {//對(duì)應(yīng)where子句,過濾
    authorId: 2,
   authorId: {
      [Sequelize.Op.eq]: 2  //操作符運(yùn)算
    }
  },
order:[], //排序
group:'name',//分組
limit:10,//限制
offset:1//頁(yè)
});

實(shí)用方法:

count,max, min 和 sum

原始查詢:

const { QueryTypes } = require('sequelize');
const users = await sequelize.query("SELECT * FROM `users`");//參數(shù)是sql語(yǔ)句

偏執(zhí)表:

Sequelize 支持 paranoid 表的概念
這意味著刪除記錄時(shí)不會(huì)真的刪除,而是給字段deletedAt值設(shè)置為時(shí)間戳
刪除的時(shí)候默認(rèn)是軟刪除,而不是硬刪除

class Post extends Model {}
Post.init({ /* 這是屬性 */ }, {
  sequelize,
  paranoid: true,// 傳遞該參數(shù),創(chuàng)建偏執(zhí)表
  // 如果要為 deletedAt 列指定自定義名稱
  deletedAt: 'destroyTime'
});

強(qiáng)制刪除:

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

軟刪除的實(shí)例,恢復(fù):

post.restore();

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

查詢包含軟刪除的記錄:

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

關(guān)聯(lián)類型:

對(duì)應(yīng) sql語(yǔ)句的 foreign key 進(jìn)行表關(guān)聯(lián)
HasOne BelongsTo HasMany BelongsToMany

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

A.hasOne(B); // A 有一個(gè) B ,外鍵在目標(biāo)模型(B)中定義
A.belongsTo(B); // A 屬于 B  ,外鍵在目標(biāo)模型(A)中定義
A.hasMany(B); // A 有多個(gè) B 外鍵在目標(biāo)模型(B)中定義
A.belongsToMany(B, { through: 'C' }); // A 屬于多個(gè) B , 通過聯(lián)結(jié)表 C

A.belongsToMany(B, { through: 'C' }) 關(guān)聯(lián)意味著將表 C 用作聯(lián)結(jié)表,在 AB 之間存在多對(duì)多關(guān)系. 具有外鍵(例如,aIdbId). Sequelize 將自動(dòng)創(chuàng)建此模型 C(除非已經(jīng)存在),并在其上定義適當(dāng)?shù)耐怄I.

創(chuàng)建標(biāo)準(zhǔn)關(guān)系:
  • 創(chuàng)建一個(gè) 一對(duì)一 關(guān)系, hasOnebelongsTo 關(guān)聯(lián)一起使用;
  • 創(chuàng)建一個(gè) 一對(duì)多 關(guān)系, hasMany he belongsTo 關(guān)聯(lián)一起使用;
  • 創(chuàng)建一個(gè) 多對(duì)多 關(guān)系, 兩個(gè) belongsToMany 調(diào)用一起使用.

添加到實(shí)例的特殊方法:

創(chuàng)建關(guān)聯(lián)關(guān)系后,這些模型的實(shí)例會(huì)獲得特殊方法
例如:有兩個(gè)模型 FooBar 擁有關(guān)聯(lián)關(guān)系,則根據(jù)關(guān)聯(lián)類型擁有以下可用方法;

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()

多對(duì)多關(guān)系:

代碼分析:

  1. 創(chuàng)建表Foo,Bar ,設(shè)置為多對(duì)多,中間表為Foo_Bar
  2. sequelize.sync();同步到數(shù)據(jù)庫(kù),就是說如果模型對(duì)應(yīng)的表不存在就創(chuàng)建
    插入數(shù)據(jù) foo, bar
  3. foo.addBar(bar) ,foo 關(guān)聯(lián)了一個(gè)bar,反映到數(shù)據(jù)庫(kù)上面,則是中間表Foo_Bar插入一條數(shù)據(jù) INSERT INTO Foo_Bar (FooId,BarId) VALUES(1,1)
  4. Foo.findOne({ include: Bar });數(shù)據(jù)查詢,根據(jù)模型,查出Foo表的第一條數(shù)據(jù),
    并帶上關(guān)聯(lián)表數(shù)據(jù),字段是Bars(因?yàn)槭嵌鄬?duì)多,所以這里是復(fù)數(shù)形式,每一條bar包含中間表的數(shù)據(jù)字段是 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這條數(shù)據(jù)關(guān)聯(lián)了一條bar,反映到表上則是在中間表Foo_Bar上插入一條數(shù)據(jù)
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
      }
    }
  ]
}

高級(jí)關(guān)聯(lián)概念

預(yù)先加載:

查詢方法中使用 include 參數(shù)完成預(yù)先加載,翻譯成sql其實(shí)就是 通過join關(guān)聯(lián)子句;

創(chuàng)建關(guān)聯(lián):

可以一次性創(chuàng)建帶關(guān)聯(lián)關(guān)系的數(shù)據(jù)

高級(jí)M:N關(guān)聯(lián):

超級(jí)多對(duì)多:

超級(jí)多對(duì)多創(chuàng)建出來(lái)的表跟多對(duì)多一樣,沒什么區(qū)別,區(qū)別就是一次使用6個(gè)關(guān)聯(lián),然后就可以進(jìn)行各種預(yù)先加載

//模型:
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 });

// 超級(jí)多對(duì)多關(guān)系
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
User.hasMany(Grant);
Grant.belongsTo(User);
Profile.hasMany(Grant);
Grant.belongsTo(Profile);

這樣,我們可以進(jìn)行各種預(yù)先加載:

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

多態(tài)關(guān)聯(lián):

多態(tài)關(guān)聯(lián),就是說一個(gè)聯(lián)結(jié)表的外鍵關(guān)聯(lián)多個(gè)表
由于外鍵引用了多個(gè)表,無(wú)法添加REFERENCES約束,需要禁用約束constraints: false

一對(duì)多的多態(tài)關(guān)聯(lián):

考慮模型 Image Video Comment ,
圖片,視頻都可以有多個(gè)評(píng)論,
但是一個(gè)評(píng)論只能是圖片跟視頻中的其中一種類型;

// 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) {//獲取評(píng)論關(guān)聯(lián)類型的那個(gè)實(shí)例
    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: {//關(guān)聯(lián)作用域  commentableType = 'image'
    commentableType: 'image'
  }
});
Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false });

Video.hasMany(Comment, {
  foreignKey: 'commentableId',
  constraints: false,
  scope: {//關(guān)聯(lián)作用域  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;
    }
    // 防止錯(cuò)誤:
     delete instance.image;
     delete instance.dataValues.image;
     delete instance.video;
     delete instance.dataValues.video;
  }
});
多對(duì)多多態(tài)關(guān)聯(lián):
class Tag extends Model {
  getTaggables(options) {
    const images = await this.getImages(options);
    const videos = await this.getVideos(options);
    // 在單個(gè) taggables 數(shù)組中合并 images 和 videos
    return images.concat(videos);
  }
}
Tag.init({
  name: DataTypes.STRING
}, { sequelize, modelName: 'tag' });

// 在這里,我們明確定義聯(lián)結(jié)模型
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: {//注意這里的作用域用于關(guān)聯(lián)模型,因?yàn)檫@scope參數(shù)在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: {//注意這里的作用域用于關(guān)聯(lián)模型,因?yàn)檫@scope參數(shù)在through下
      taggableType: 'video'
    }
  },
  foreignKey: 'taggableId',
  constraints: false
});
Tag.belongsToMany(Video, {
  through: {
    model: Tag_Taggable,
    unique: false
  },
  foreignKey: 'tagId',
  constraints: false
});
在目標(biāo)模型上應(yīng)用作用域

我們還可以在目標(biāo)模型上應(yīng)用關(guān)聯(lián)作用域. 我們甚至可以同時(shí)進(jìn)行,以下實(shí)例:

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

其他主題

事務(wù):

Sequelize 支持兩種使用事務(wù)的方式:

  1. 非托管事務(wù): 提交和回滾事務(wù)應(yīng)由用戶手動(dòng)完成(通過調(diào)用適當(dāng)?shù)?Sequelize 方法).
  2. 托管事務(wù): 如果引發(fā)任何錯(cuò)誤,Sequelize 將自動(dòng)回滾事務(wù),否則將提交事務(wù). 另外,如果啟用了CLS(連續(xù)本地存儲(chǔ)),則事務(wù)回調(diào)中的所有查詢將自動(dòng)接收事務(wù)對(duì)象.
非托管事務(wù):
// 首先,我們開始一個(gè)事務(wù)并將其保存到變量中
const t = await sequelize.transaction();

try {

  // 然后,我們進(jìn)行一些調(diào)用以將此事務(wù)作為參數(shù)傳遞:

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

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

  // 如果執(zhí)行到此行,且沒有引發(fā)任何錯(cuò)誤.
  // 我們手動(dòng)提交事務(wù).
  await t.commit();

} catch (error) {

  // 如果執(zhí)行到達(dá)此行,則拋出錯(cuò)誤.
  // 我們回滾事務(wù).
  await t.rollback();

}
托管事務(wù):
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;

  });

  // 如果執(zhí)行到此行,則表示事務(wù)已成功提交,`result`是事務(wù)返回的結(jié)果
  // `result` 就是從事務(wù)回調(diào)中返回的結(jié)果(在這種情況下為 `user`)

} catch (error) {

  // 如果執(zhí)行到此,則發(fā)生錯(cuò)誤.
  // 該事務(wù)已由 Sequelize 自動(dòng)回滾!

}

作用域:

不同于關(guān)聯(lián)作用域, 作用域定義在模型中,幫助重用代碼
作用域在模型定義中定義,可以是查找器對(duì)象,也可以是返回查找器對(duì)象的函數(shù) - 默認(rèn)作用域除外,該作用域只能是一個(gè)對(duì)象

class Project extends Model {}
Project.init({
  // 屬性
}, {
  defaultScope: {//默認(rèn)作用域
    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(); //用法就是調(diào)用scope方法傳入字符串,返回一個(gè)查詢對(duì)象
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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容