12

Sequelize官网翻译九 —— 关联

 3 years ago
source link: https://zhuanlan.zhihu.com/p/125023957
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Sequelize官网翻译九 —— 关联

Sequelize支持标准的关联关系: 一对一,一对多,多对多。

Sequelize里可以创建四种关联类型

  • HasOne
  • BelongsTo
  • HasMany
  • BelongsToMany

本指南将会解释如何定义这四种关联类型,然后解释如何把它们和标准的关联关系(一对一,一对多,多对多)结合起来。

定义Sequelize关联

四种关联类型定义的方式都差不多。假如我们有两个模型AB。告诉Sequelize你想在两者之间建立关联

 const A = sequelize.define('A', /* ... */);
 const B = sequelize.define('B', /* ... */);
 ​
 A.hasOne(B); // A HasOne B
 A.belongsTo(B); // A BelongsTo B
 A.hasMany(B); // A HasMany B
 A.belongsToMany(B, { through: 'C' }); // A BelongsToMany B through the junction table C

它们都接受一个参数对象作为第二个参数,至少要包含through字段

 A.hasOne(B, { /* options */ });
 A.belongsTo(B, { /* options */ });
 A.hasMany(B, { /* options */ });
 A.belongsToMany(B, { through: 'C', /* options */ });

关联关系定义的次序也是有讲究的。在上面的例子中,A被称为源模型而B被称为目标模型。这个术语概念很重要。

A.hasOne(B)表示AB之间有一对一的关系,外键定义在目标模型B上。

A.belongsTo(B)表示AB之间有一对一的关系,外键定义在源模型A上。

A.hasMany(B)表示AB之间有一对多的关系,外键定义在目标模型B上。

这三个调用将会引起Sequelize自动给相应的模型上添加外键(除非外键已经存在了)。

A.belongsToMany(B, {through: 'C'})表示AB之间有多对多的关系,使用表C作为连接表,表C有关联的外键(比如aIdbId)。Sequelize将会自动创建模型C(除非它已经存在了)并且在其上定义外键。

注意上面的belongsToMany例子中,字符串C被作为参数传入。这种情况下,Sequelize会自动创建名为C的模型。但你也可以直接传入一个已经定义好的模型。

这些就是关联的主要概念。其实这些关联关系都是成对使用的,为的是更好利用起Sequelize。稍后将见分晓。

创建标准关联

如前所述,通常Sequelize关联关系都是成对定义的,总结如下

  • 如果要创建一对一的关系,通常使用hasOnebelongsTo
  • 如果要创建一对多的关系,通常使用hasManybelongsTo
  • 如果要创建多对多的关系,通常使用两个belongsToMany

稍后会看一下细节,至于为什么要成对使用而不是单独使用将会在本章最后说明。

一对一关系

机制

在深入使用之前,先看一下一对一关联内部发生了什么。

假设有两个模型FooBar。我们想要在两者之间建立一对一的关联。我们知道在关系型数据库中,这是通过其中一个表的外键来实现的。此时,一个问题是: 外键应该建立在哪个表上?换句话说,是我们想在Foo表上有一个barId的字段还是Bar表上有一个fooId的字段?

原则上,两者都是可行的。但是,当我们说"FooBar之间存在一对一的关联"时,关联是必须的或是可选的都还不明确。即,Foo可以脱离Bar存在吗?Bar可以脱离Foo存在吗?答案将会帮助我们理清外键到底应该怎么设置。

目标

剩下的例子中,假设我们有FooBar的模型。我们要在它们之上建立起一对一的关系,在Bar有一个fooId字段。

实现

主要的步骤如下

 Foo.hasOne(Bar);
 Bar.belongsTo(Foo);

由于没有传入配置参数,Sequelize将会从模型名字中进行推断。此时Sequelize将知道要把fooId加到Bar表中去。

然后调用Bar.sync()时,会生成如下的SQL(以PostgreSQL为例)

 CREATE TABLE IF NOT EXISTS "foos" (
   /* ... */
 );
 CREATE TABLE IF NOT EXISTS "bars" (
   /* ... */
   "fooId" INTEGER REFERENCES "foos" ("id") ON DELETE SET NULL ON UPDATE CASCADE
   /* ... */
 );

参数

onDelete和onUpdate

如果要配置ON DELETEON UPDATE行为

 Foo.hasOne(Bar, {
   onDelete: 'RESTRICT',
   onUpdate: 'RESTRICT'
 });
 Bar.belongsTo(Foo);

选项有RESTRICT CASCADE NO ACTION SET DEFAULT SET NULL

一对一关联默认在ON DELETE上使用SET NULL,在ON UPDATE上使用CASCADE

定制外键

上面的hasOnebelongsTo调用将会推断外键名为fooId。如果要用一个别的名字比如myFooId

 // Option 1
 Foo.hasOne(Bar, {
   foreignKey: 'myFooId'
 });
 Bar.belongsTo(Foo);
 ​
 // Option 2
 Foo.hasOne(Bar, {
   foreignKey: {
     name: 'myFooId'
   }
 });
 Bar.belongsTo(Foo);
 ​
 // Option 3
 Foo.hasOne(Bar);
 Bar.belongsTo(Foo, {
   foreignKey: 'myFooId'
 });
 ​
 // Option 4
 Foo.hasOne(Bar);
 Bar.belongsTo(Foo, {
   foreignKey: {
     name: 'myFooId'
   }
 });

foreginKey参数能接受一个字符串或对象。当接受对象时,这个对象将会被用来定义表字段,跟标准的sequelize.define调用一样。指定typeallowNulldefaultValue等配置也同样有效。

举例来说,如果想使用UUID作为外键数据类型而不是默认的INTEGER,你可以

 const { DataTypes } = require("Sequelize");
 ​
 Foo.hasOne(Bar, {
   foreignKey: {
     // name: 'myFooId'
     type: DataTypes.UUID
   }
 });
 Bar.belongsTo(Foo);

强制和可选的关联

关联关系默认是可选的,即fooId可以是null,也即一个Bar的记录可以不在Foo表里存在。

要修改只要在外键参数上指定allowNull: false

 Foo.hasOne(Bar, {
   foreignKey: {
     allowNull: false
   }
 });
 // "fooId" INTEGER NOT NULL REFERENCES "foos" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT

一对多关联

机制

一对多关联把一个源和多个目标之间建立起关系。

跟一对一关联不同,我们不可以选在外键在哪里放置,在一对多关联中只能放在目标表上。比如如果一个Foo对应多个Bar,那么fooId只能放在Bar表上。反过来根本不可行。

目标

假设我们有模型TeamPlayer。我们想要在两者之间建立一对多的关联,也就是一个团队有多个选手,一个选手只属于一个团队。

实现

 Team.hasMany(Player);
 Player.belongsTo(Team);

主要就是调用hasManeybelongsTo方法。

比如在PostgreSQL里上面的调用就会生成SQL语句如下

 CREATE TABLE IF NOT EXISTS "Teams" (
   /* ... */
 );
 CREATE TABLE IF NOT EXISTS "Players" (
   /* ... */
   "TeamId" INTEGER REFERENCES "Teams" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
   /* ... */
 );

参数

这里的参数跟一对一关联一样。比如要改变外键的名字

 Team.hasMany(Player, {
   foreignKey: 'clubId'
 });
 Player.belongsTo(Team);

跟一对一关联默认一样,在ON DELETE上使用SET NULL,在ON UPDATE上使用CASCADE

多对多关联

机制

多对多关联把一个源和多个目标连接起来,同时也把一个目标连接上多个源。

仅在一个表上用外键就无法表示了。此时就要用到连接模型的概念。这是一个另外的模型,它有两个外键来跟踪关联关系。连接表也被称为join tablethrough table

目标

假设我们有MovieActor的模型。一个演员可以出演多部电影,一步电影里也有多个演员。连接表叫ActorMovies,包含了两个外键movieIdactorId

实现

 const Movie = sequelize.define('Movie', { name: DataTypes.STRING });
 const Actor = sequelize.define('Actor', { name: DataTypes.STRING });
 Movie.belongsToMany(Actor, { through: 'ActorMovies' });
 Actor.belongsToMany(Movie, { through: 'ActorMovies' });

through参数里传了一个字符串,Sequelize将会自动创建ActorMovies模型来连接两张表。在PostgreSQL中会生成如下SQL

 CREATE TABLE IF NOT EXISTS "ActorMovies" (
   "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
   "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
   "MovieId" INTEGER REFERENCES "Movies" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   "ActorId" INTEGER REFERENCES "Actors" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY ("MovieId","ActorId")
 );

除了字符串,也可以直接传入一个模型作为连接模型。比如

 const Movie = sequelize.define('Movie', { name: DataTypes.STRING });
 const Actor = sequelize.define('Actor', { name: DataTypes.STRING });
 const ActorMovies = sequelize.define('ActorMovies', {
   MovieId: {
     type: DataTypes.INTEGER,
     references: {
       model: Movie, // 'Movies' would also work
       key: 'id'
     }
   },
   ActorId: {
     type: DataTypes.INTEGER,
     references: {
       model: Actor, // 'Actors' would also work
       key: 'id'
     }
   }
 });
 Movie.belongsToMany(Actor, { through: 'ActorMovies' });
 Actor.belongsToMany(Movie, { through: 'ActorMovies' });

会在PostgreSQL里生成如下的SQL

 CREATE TABLE IF NOT EXISTS "ActorMovies" (
   "MovieId" INTEGER NOT NULL REFERENCES "Movies" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
   "ActorId" INTEGER NOT NULL REFERENCES "Actors" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
   "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
   "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
   UNIQUE ("MovieId", "ActorId"),     -- Note: Sequelize generated this UNIQUE constraint but
   PRIMARY KEY ("MovieId","ActorId")  -- it is irrelevant since it's also a PRIMARY KEY
 );

参数

在多对多关联中,ON UPDATEON DElETE默认都是CASCADE

带有关联的基础查询

有了上面定义关联的基础,我们现在可以看一下带关联的操作了。最常见的就是读操作。后面也会演示其他操作。

我们假设有ShipCaptain的模型,它们之间有偶一对一的关联。主键可以为空,表示一艘船没有船长,反过来船长也没有船。

 // This is the setup of our models for the examples below
 const Ship = sequelize.define('ship', {
   name: DataTypes.TEXT,
   crewCapacity: DataTypes.INTEGER,
   amountOfSails: DataTypes.INTEGER
 }, { timestamps: false });
 const Captain = sequelize.define('captain', {
   name: DataTypes.TEXT,
   skillLevel: {
     type: DataTypes.INTEGER,
     validate: { min: 1, max: 10 }
   }
 }, { timestamps: false });
 Captain.hasOne(Ship);
 Ship.belongsTo(Captain);

预加载和懒加载

预加载和懒加载的概念是理解Sequelize里如何操作数据的基础。懒加载就是只在真正想要数据的时候才获得它,而预加载总是一次性获得所有的数据。

懒加载例子

 const awesomeCaptain = await Captain.findOne({
   where: {
     name: "Jack Sparrow"
   }
 });
 // Do stuff with the fetched captain
 console.log('Name:', awesomeCaptain.name);
 console.log('Skill Level:', awesomeCaptain.skillLevel);
 // Now we want information about his ship!
 const hisShip = await awesomeCaptain.getShip();
 // Do stuff with the ship
 console.log('Ship Name:', hisShip.name);
 console.log('Amount of Sails:', hisShip.amountOfSails);

上面的例子我们查询两次,只在想要使用关联的船数据时才会真正去取。在不确定是否要获取数据时懒加载会很有用,可以节约时间和金钱。

上面getShip()方法是由Sequelize自动添加给Caption实例的。其他也有这种情况,后续将学到它们。

预加载例子

 const awesomeCaptain = await Captain.findOne({
   where: {
     name: "Jack Sparrow"
   },
   include: Ship
 });
 // Now the ship comes with it
 console.log('Name:', awesomeCaptain.name);
 console.log('Skill Level:', awesomeCaptain.skillLevel);
 console.log('Ship Name:', awesomeCaptain.ship.name);
 console.log('Amount of Sails:', awesomeCaptain.ship.amountOfSails);

include生成预加载。注意针对数据库只查询了一次。

这仅是对预加载的简单介绍,详细的可以在这里继续学习。

新建、更新和删除

  • 使用标准模型操作
 // Example: creating an associated model using the standard methods
 Bar.create({
   name: 'My Bar',
   fooId: 5
 });
 // This creates a Bar belonging to the Foo of ID 5 (since fooId is
 // a regular column, after all). Nothing very clever going on here.
  • 或使用后文介绍的特殊方法/mixin

save()实例方法不感知关联关系,也就是如果改变了从属于某个父对象上的子对象的值,在父对象上调用save()时将忽略子对象上的更改。

关联别名和定制外键

上面的例子都是Sequelize自动定义了外键名字。比如在ShipCaptain的例子中,Sequelize自动添加了captainIdShip的模型上。要定制的话也很简单。

假设以一种简单的形式定义模型,仅关注于当前的话题

 const Ship = sequelize.define('ship', { name: DataTypes.TEXT }, { timestamps: false });
 const Captain = sequelize.define('captain', { name: DataTypes.TEXT }, { timestamps: false });

有三种方式给外键指定另一个名字

  • 直接提供外键名字
  • 定义一个别名
  • 同时做上述两件事情

回顾默认的行为

调用Ship.belongsTo(Captain)时,sequelize自动生成外键名字

 Ship.belongsTo(Captain); // This creates the `captainId` foreign key in Ship.
 ​
 // Eager Loading is done by passing the model to `include`:
 console.log((await Ship.findAll({ include: Captain })).toJSON());
 // Or by providing the associated model name:
 console.log((await Ship.findAll({ include: 'captain' })).toJSON());
 ​
 // Also, instances obtain a `getCaptain()` method for Lazy Loading:
 const ship = Ship.findOne();
 console.log((await ship.getCaptain()).toJSON());

直接提供外键名字

 Ship.belongsTo(Captain, { foreignKey: 'bossId' }); // This creates the `bossId` foreign key in Ship.
 ​
 // Eager Loading is done by passing the model to `include`:
 console.log((await Ship.findAll({ include: Captain })).toJSON());
 // Or by providing the associated model name:
 console.log((await Ship.findAll({ include: 'Captain' })).toJSON());
 ​
 // Also, instances obtain a `getCaptain()` method for Lazy Loading:
 const ship = Ship.findOne();
 console.log((await ship.getCaptain()).toJSON());

定义别名

 Ship.belongsTo(Captain, { as: 'leader' }); // This creates the `leaderId` foreign key in Ship.
 ​
 // Eager Loading no longer works by passing the model to `include`:
 console.log((await Ship.findAll({ include: Captain })).toJSON()); // Throws an error
 // Instead, you have to pass the alias:
 console.log((await Ship.findAll({ include: 'leader' })).toJSON());
 // Or you can pass an object specifying the model and alias:
 console.log((await Ship.findAll({
   include: {
     model: Captain,
     as: 'leader'
   }
 })).toJSON());
 ​
 // Also, instances obtain a `getLeader()` method for Lazy Loading:
 const ship = Ship.findOne();
 console.log((await ship.getLeader()).toJSON());

当想要在同一个模型上定义两个不同的关联时,使用别名会很有用。比如,我们有MailPerson的模型,我们可以把它们关联两次,代表接收者和发送者。这种情况我们必须定义关联的别名,否则像mail.getPerson()这种调用将会产生歧义。有了senderreceiver别名,我们有两个mail.getSender()mail.getReceiver()方法,它们都返回同一个Promise<Person>

当定义hasOnebelongsTo关联的别名,应该使用单一的词,比如leader,当定义hasManybelongsToMany的别名,应该使用复合的词。当定义多对多关系的别名时,遵守这个规则

同时提供

 Ship.belongsTo(Captain, { as: 'leader', foreignKey: 'bossId' }); // This creates the `bossId` foreign key in Ship.
 ​
 // Since an alias was defined, eager Loading doesn't work by simply passing the model to `include`:
 console.log((await Ship.findAll({ include: Captain })).toJSON()); // Throws an error
 // Instead, you have to pass the alias:
 console.log((await Ship.findAll({ include: 'leader' })).toJSON());
 // Or you can pass an object specifying the model and alias:
 console.log((await Ship.findAll({
   include: {
     model: Captain,
     as: 'leader'
   }
 })).toJSON());
 ​
 // Also, instances obtain a `getLeader()` method for Lazy Loading:
 const ship = Ship.findOne();
 console.log((await ship.getLeader()).toJSON());

实例上特别的方法/mixin

当两个模型之间有关联时,模型实例上会被加入一些特别地方法来跟关联的模型交互。

FooBar举例,如果它们关联起来,它们的实例上将获得如下方法

Foo.hasOne(Bar)

  • fooInstance.getBar()
  • fooInstance.setBar()
  • 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.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.belongsTo(Bar)

  • fooInstance.getBar()
  • fooInstance.setBar()
  • fooInstance.createBar()

Foo.hasMany(Bar)

  • 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([]); // Un-associate all previously associated bars
 console.log(await foo.countBars()); // 0

getter方法跟一般的finder(比如findAll)方法接受一样的参数。

 const easyTasks = await project.getTasks({
   where: {
     difficulty: {
       [Op.lte]: 5
     }
   }
 });
 const taskTitles = (await project.getTasks({
   attributes: ['title'],
   raw: true
 })).map(task => task.title);

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

注意方法名字

如上面的例子所示,这些特别的方法都有像getsetadd这样的前缀,再加上关联的模型的名字(首字母变为大写)。必要时用上复数,比如fooInstance.setBars()。不规则的复数变化由Sequelize自己来处理,像Person变为PeopleHypothesis变为Hypotheses

如果定义了别名,那就会使用别名而不是模型的名字。

 Task.hasOne(User, { as: 'Author' });
  • taskInstance.getAuthor()
  • taskInstance.setAuthor()
  • taskInstance.createAuthor()

为什么关联是要成对的?

之前提到关联要成对出现

  • 如果要创建一对一的关系,通常使用hasOnebelongsTo
  • 如果要创建一对多的关系,通常使用hasManybelongsTo
  • 如果要创建多对多的关系,通常使用两个belongsToMany

当在两个模型之间定义关联时,只有源模型才会知道它。因此当使用Foo.hasOne(Bar)时,只有Foo知道关联模型的存在。然后Foo将会有getBar()setBar()createBar()这些方法,而Bar什么都没有。

相似的,对于Foo.hasOne(Bar),由于Foo知道关联关系,我们可以执行Foo.findOne({include: Bar})来预加载,而不能执行Bar.findOne({include: Foo})

所以要充分利用起Sequelize,我们要成对地建立关联关系,让两个模型都感知对方的存在。

  • 如果不成对定义关联,仅是Foo.hasOne(Bar)
 // This works...
 await Foo.findOne({ include: Bar });
 ​
 // But this throws an error:
 await Bar.findOne({ include: Foo });
 // SequelizeEagerLoadingError: foo is not associated to bar!
  • 如果成对定义了关联,同时有Foo.hasOne(Bar)Bar.belongsTo(Foo)
 // This works!
 await Foo.findOne({ include: Bar });
 ​
 // This also works!
 await Bar.findOne({ include: Foo });

相同模型的多个关联

Sequelize里支持对同一个模型定义多个关联关系,只要设置好别名就行。

 Team.hasOne(Game, { as: 'HomeTeam', foreignKey: 'homeTeamId' });
 Team.hasOne(Game, { as: 'AwayTeam', foreignKey: 'awayTeamId' });
 Game.belongsTo(Team);

基于非主键创建引用关系

上面所有的例子都是用主键来做外键的,当然也可以在非主键字段上定义关联。这些字段必须得要有唯一性约束,否则没有意义。

对于belongsTo

首先回顾一下A.belongsTo(B)关联会将外键建立在源模型A上。

再次使用ShipCaptain的模型,我们假设船长的名字是唯一的

 const Ship = sequelize.define('ship', { name: DataTypes.TEXT }, { timestamps: false });
 const Captain = sequelize.define('captain', {
   name: { type: DataTypes.TEXT, unique: true }
 }, { timestamps: false });

这样子,除了captainId,我们还能用captainName作为关联的纽带。即除了引用到Captain模型的id字段,我们的关联还可以引用目标模型Captainname字段。我们要用targetKey来定义这个外键

 Ship.belongsTo(Captain, { targetKey: 'name', foreignKey: 'captainName' });
 // This creates a foreign key called `captainName` in the source model (Ship)
 // which references the `name` field from the target model (Captain).

现在我们可以这么做

 await Captain.create({ name: "Jack Sparrow" });
 const ship = await Ship.create({ name: "Black Pearl", captainName: "Jack Sparrow" });
 console.log((await ship.getCaptain()).name); // "Jack Sparrow"

对于hasOnehasMany

它们也是一样的,但是是用sourceKey参数。因为不同于belongsTohasOnehasMany关联关系的外键是建在目标模型上的。

 const Foo = sequelize.define('foo', {
   name: { type: DataTypes.TEXT, unique: true }
 }, { timestamps: false });
 const Bar = sequelize.define('bar', {
   title: { type: DataTypes.TEXT, unique: true }
 }, { timestamps: false });
 const Baz = sequelize.define('baz', { summary: DataTypes.TEXT }, { timestamps: false });
 Foo.hasOne(Bar, { sourceKey: 'name', foreignKey: 'fooName' });
 Bar.hasMany(Baz, { sourceKey: 'title', foreignKey: 'barTitle' });
 // [...]
 await Bar.setFoo("Foo's Name Here");
 await Baz.addBar("Bar's Title Here");

对于belongsToMany

不同是belongsToMany有两个外键并存在另外的表里。

 const Foo = sequelize.define('foo', {
   name: { type: DataTypes.TEXT, unique: true }
 }, { timestamps: false });
 const Bar = sequelize.define('bar', {
   title: { type: DataTypes.TEXT, unique: true }
 }, { timestamps: false });

要考虑四种场景

  • FooBar上用两者的主键创建多对多的关联
 Foo.belongsToMany(Bar, { through: 'foo_bar' });
 // This creates a junction table `foo_bar` with fields `fooId` and `barId`
  • Foo上用主键但是Bar上用非主键字段来创建多对多的关联
 Foo.belongsToMany(Bar, { through: 'foo_bar', targetKey: 'title' });
 // This creates a junction table `foo_bar` with fields `fooId` and `barTitle`
  • Foo上用非主键字段而Bar上用主键创建多对多的关联
 Foo.belongsToMany(Bar, { through: 'foo_bar', sourceKey: 'name' });
 // This creates a junction table `foo_bar` with fields `fooName` and `barId`
  • FooBar都用非主键字段创建多对多的关联
 Foo.belongsToMany(Bar, { through: 'foo_bar', sourceKey: 'name', targetKey: 'title' });
 // This creates a junction table `foo_bar` with fields `fooName` and `barTitle`

注意事项

不要忘了关联关系使用的字段必须其上有唯一性约束,否则将会抛出错误。

选用sourceKey还是targetKey的技巧在于记住在哪的位置放置外键,正如开头提到的

  • A.belongsTo(B)的外键在A上,使用targetKey
  • A.hasOne(B)A.hasMany(B)的外键在B上,使用sourceKey
  • A.belongsToMany(B)包含了另外的表,所以targetKeysourceKey都会用到,sourceKey用在A的对应字段上,targetKey用在B的对应字段上

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK