Sequelize关联(就是联表操作)存在三种关系:
- 一对一
- 一对多
- 多对多
同时提供了四种关联类型,我们可以将其组合创建关联: - HasOne
- BelongsTo
- HasMany
- BelongsToMany
A.hasOne(B, { /* 参数 */ }); // A 有一个 B
A.belongsTo(B, { /* 参数 */ }); // A 属于B
A.hasMany(B, { /* 参数 */ }); // A 有多个 B
A.belongsToMany(B, { through: 'C', /* 参数 */ }); // A 属于多个 B , 通过联结表 C
解析: 具体怎么用后面解析,先弄清一个概念:A(前者)称为源模型,B(后者)称为目标模型
我们先定义一个简单的例子,后续的操作都是基于该例子:
我们分别定义了一个船长(Captain)和一艘船(Ship)的model。
const sequelize = require('./db').OAWebSite
const { DataTypes } = require('sequelize');
// 这是我们用于以下示例的模型的设置
const Ship = sequelize.define('ship', {
name: { type: DataTypes.STRING, unique: true },
crewCapacity: DataTypes.INTEGER,
amountOfSails: DataTypes.INTEGER
}, { timestamps: false });
const Captain = sequelize.define('captain', {
name: { type: DataTypes.STRING, unique: true },
skillLevel: {
type: DataTypes.INTEGER,
validate: { min: 1, max: 10 }
}
}, { timestamps: false });
一对一
hasOne和belongsTo联用:
例如:我们定义一艘船只有一个船长,一个船长只开一艘船。这里我们需要定义外键的位置:我们需要考虑的是是否能为空,例如我们这将外键设在Ship上。(船可以在没有船长的情况下存在)
Captain.hasOne(Ship);
Ship.belongsTo(Captain);
自定义外键
这时我们再调用Ship.sync()就可以发现Ship表里面多了一个外键(captainId)。当然我们可以通过指定外键的名字:
Captain.hasOne(Ship, {
foreignKey: 'myShip'
});
// =>
Captain.hasOne(Ship, {
foreignKey: {
name: 'myShip',
as: 'gg' // 别名
}
});
还有很多方法指定外键的名字,甚至指定其type,allowNull,defaultValue等就不一一举例。如果需要强关联型,例如船长必须有船,则可以指定其外键不允许为null即可。
Captain.hasOne(Ship, {
foreignKey: {
name: 'myShip',
allowNull: false
}
});
一对多
一对多关联将一个源与多个目标连接,而所有这些目标仅与此单个源连接.
简单来说就是一个船长他可以开多艘船(just as wang校长有多辆车一个道理)。用法和配置和一对一几乎一样。
Captain.hasMany(Ship);
Ship.belongsTo(Captain);
多对多
多对多关联将一个源与多个目标相连,而所有这些目标又可以与第一个目标之外的其他源相连.
简单来说就像共享汽车吧,一个人可以开过多辆车,一辆车被多个人开过。
Captain.belongsToMany(Ship, { through: 'ShipCaptains' });
Ship.belongsToMany(Captain, { through: 'ShipCaptains' });
解析,这里将生产一个新表ShipCaptains并且外键默认为(‘shipId’, ‘captainId’)
特殊方法
查询实例
这里可以通过include来查找关联的数据表信息,默认查询是不包括联表信息
Captain.hasOne(Ship);
Ship.belongsTo(Captain);
// 查询所有联表信息
console.log(await Captain.findAll({ include: Ship }).toJSON())
// 获取其对应关系表信息
const awesomeCaptain = await Captain.findOne({
where: {
name: "Jack Sparrow"
}
});
awesomeCaptain.getShip()
解析:上面的getShip()方法是联表后,sequelize会自动根据联表对象的实例名天津一些方法到Captain对象上。如果定义了别名,则需要根据别名来获取。
...
Ship.belongsTo(Captain, { as: 'leader' });
...
const ship = Ship.findOne();
console.log((await ship.getLeader()).toJSON());
预加载和延迟加载
预先加载: 是在findAll的时候直接include关联对象,这时候一开始就获取了所有对象和其关联的对象。
**延迟加载:**是先不获取其关联对象,后续再使用类似getShip的一些内部方法获取到其关联的随性。
Foo.hasOne(Bar) & Foo.belongsTo(Bar)
- foo.getBar() 获取关联对象信息
- foo.setBar() 设置关联对象信息
- foo.createBar() 创建关联对象信息
用法理解起来很简单
const foo = await Foo.create({ name: 'the-foo' });
const bar1 = await Bar.create({ name: 'some-bar' });
const bar2 = await Bar.create({ name: 'another-bar' });
console.log(await foo.getBar()); // null
await foo.setBar(bar1);
console.log((await foo.getBar()).name); // 'some-bar'
await foo.createBar({ name: 'yet-another-bar' });
const newlyAssociatedBar = await foo.getBar();
console.log(newlyAssociatedBar.name); // 'yet-another-bar'
await foo.setBar(null); // Un-associate
console.log(await foo.getBar()); // null
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() 创建关联对象
具体用法和上面基本一致
const foo = await Foo.create({ name: 'the-foo' });
const bar1 = await Bar.create({ name: 'some-bar' });
const bar2 = await Bar.create({ name: 'another-bar' });
console.log(await foo.getBars()); // []
console.log(await foo.countBars()); // 0
console.log(await foo.hasBar(bar1)); // false
await foo.addBars([bar1, bar2]);
console.log(await foo.countBars()); // 2
await foo.addBar(bar1);
console.log(await foo.countBars()); // 2
console.log(await foo.hasBar(bar1)); // true
await foo.removeBar(bar2);
console.log(await foo.countBars()); // 1
await foo.createBar({ name: 'yet-another-bar' });
console.log(await foo.countBars()); // 2
await foo.setBars([]); // 取消关联所有先前关联的 Bars
console.log(await foo.countBars()); // 0
为什么关联是成对定义的?
当在两个模型之间定义了 Sequelize 关联时,只有 源 模型 知晓关系. 因此,例如,当使用 Foo.hasOne(Bar)(当前,Foo 是源模型,而 Bar 是目标模型)时,只有 Foo 知道该关联的存在. 这就是为什么在这种情况下,如上所示,Foo 实例获得方法 getBar(), setBar() 和 createBar() 而另一方面,Bar 实例却没有获得任何方法.
指定关联主键
默认情况下都是以关联对象表的id主键作为外键,我们可以定义sourceKey(源模型key)或targetKey(目标模型key)。具体看外键设在哪,例如A.hasOne(B)和A.hasMany(B) 外键都是设置在B(目标模型)上,所以我们要指定的是源模型key.
A.hasOne(B, { sourceKey: 'name', foreignKey: 'fooName' });
A.hasMany(B, { sourceKey: 'title', foreignKey: 'barTitle' });
如果是belongsTo关系的话,则需要设置在目标模型上,
A.belongsTo(B, { targetKey: 'name', foreignKey: 'captainName' });
这里再次强调,无论哪种都是前者是源模型,后者是目标模型。
但如果是belongsToMany则其和前面的不同,前面只有一个外键,这里可以配置两个或只配一个或都不配。不配置的默认以关联对象表的id主键作为外键。
A.belongsToMany(B, { through: 'foo_bar', sourceKey: 'name', targetKey: 'title' });