1、Relations / Associations
1.7 关系检查(Check associations)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Project.create({ }).then(function(project) { return User.create({ }).then(function(user) { return project.hasUser(user).then(function(result) { return project.addUser(user).then(function() { return project.hasUser(user).then(function(result) { }) }) }) }) }) project.setUsers([user1, user2]).then(function() { return project.hasUsers([user1]); }).then(function(result) { return project.hasUsers([user1, user2]); }).then(function(result) { })
|
1.8 外键(Foreign Keys)
Sequelize中,如果创建了两个模型之间的关联,那么相关联的外键会被自动创建:
1 2 3 4
| var Task = this.sequelize.define('task', { title: Sequelize.STRING }) , User = this.sequelize.define('user', { username: Sequelize.STRING }) User.hasMany(Task) Task.belongsTo(User)
|
会生成以下SQL 语句:
1 2 3 4 5 6 7 8 9 10
| CREATE TABLE IF NOT EXISTS `User` ( `id` INTEGER PRIMARY KEY, `username` VARCHAR(255) ); CREATE TABLE IF NOT EXISTS `Task` ( `id` INTEGER PRIMARY KEY, `title` VARCHAR(255), `user_id` INTEGER REFERENCES `User` (`id`) ON DELETE SET NULL ON UPDATE CASCADE );
|
task 和 user之间的关系通过task表的外键user_id引入,并REFERENCES引用user表。默认情况下,引用的user 如果被删除那么user_id会被设置为NULL,并且会随user id的更新而更新。这些选项可以在建立关系时通过MonUpdate和onDelete选项修改。可选项有:
1
| RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
|
对于 1:1 和 1:m 的关系,默认的删除选项是SET NULL,更新选项是CASCADE。而 n:m 的关系,两者都是CASCADE。这意味着,你从 n:m 关系的任一方删除或更新数据,其所对应的关联数据也会同时被删除或更新。
添加表之间的约束意味着,使用sequelize.sync创建表时,相关的表在数据库中必须有一定创建顺序。如果Task 表引用了User,那么User 必须在Task 之前创建。有时这会导致循环引用,想象一个场景:一个文档和版本,一个文档可以有多个版本,并且为了方便,文档对它的当前版本有一个引用。
1 2 3 4 5 6 7 8 9
| var Document = this.sequelize.define('document', { author: Sequelize.STRING }) , Version = this.sequelize.define('version', { timestamp: Sequelize.DATE }) Document.hasMany(Version) Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id'})
|
这时可能出现类似以下错误:
1
| Cyclic dependency found. 'Document' is dependent of itself. Dependency Chain: Document -> Version => Document
|
为了解决这个问题,可以传入一个constraints: false选项:
1 2
| Document.hasMany(Version) Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id', constraints: false})
|
这时会按以下顺序将表同步到数据库中:
1 2 3 4 5 6 7 8 9 10
| CREATE TABLE IF NOT EXISTS `Document` ( `id` INTEGER PRIMARY KEY, `author` VARCHAR(255), `current_version_id` INTEGER ); CREATE TABLE IF NOT EXISTS `Version` ( `id` INTEGER PRIMARY KEY, `timestamp` DATETIME, `document_id` INTEGER REFERENCES `Document` (`id`) ON DELETE SET NULL ON UPDATE CASCADE );
|
没有约束的外键引用
有时我们想添加一个外键引用,但不想添加任何约束或关系。这种情形下,你可以在schema定义时手动添加reference属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| var Series, Trainer, Video Series = sequelize.define('series', { title: DataTypes.STRING, sub_title: DataTypes.STRING, description: DataTypes.TEXT, trainer_id: { type: DataTypes.INTEGER, references: { model: "trainers", key: "id" } } }) Trainer = sequelize.define('trainer', { first_name: DataTypes.STRING, last_name: DataTypes.STRING }); Video = sequelize.define('video', { title: DataTypes.STRING, sequence: DataTypes.INTEGER, description: DataTypes.TEXT, series_id: { type: DataTypes.INTEGER, references: { model: Series, key: "id" } } }); Series.hasOne(Video); Trainer.hasMany(Series);
|
2、Transaction
2.1、 事务的使用
Sequelize有两种使用事务的方式:
基于Promise结果链的自动提交/回滚
另一种是不自动提交和回滚,而由用户控制事务
2.1.1 受管理的事务(auto-callback)
受管理的事务会自动提交或回滚,你可以向sequelize.transaction方法传递一个回调函数来启动一个事务。
需要注意,在这种方式下传递给回调函数的transaction会返回一个promise链,在promise链中(then或catch)中并不能调用t.commit()或t.rollback()来控制事务。在这种方式下,如果使用事务的所有promise链都执行成功,则自动提交;如果其中之一执行失败,则自动回滚。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| return sequelize.transaction(function (t) { return User.create({ firstName: 'Abraham', lastName: 'Lincoln' }, {transaction: t}).then(function (user) { return user.setShooter({ firstName: 'John', lastName: 'Boothe' }, {transaction: t}); }); }).then(function (result) { }).catch(function (err) { });
|
抛出错误并回滚
使用受管理的事务时,不能通过手工调用的方式来提交或回滚事务。但在需要时(如验证失败),可以通过throw来抛出异常回滚事务。
1 2 3 4 5 6 7 8 9
| return sequelize.transaction(function (t) { return User.create({ firstName: 'Abraham', lastName: 'Lincoln' }, {transaction: t}).then(function (user) { throw new Error(); }); });
|
自动传递事务到所有的查询中
在上面的示例中,我们通过向第二个参数中添加{ transaction: t }选项来手工传递事务。如果要自动传递事务到所有的查询中,需要安装continuation local storage(CLS)模块并在代码中创建一个命名空间实例:
1 2
| var cls = require('continuation-local-storage'), namespace = cls.createNamespace('my-very-own-namespace');
|
启用CLS,需要在Sequlize构造函数属性中设置命名空间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var Sequelize = require('sequelize'); Sequelize.cls = namespace; new Sequelize(....); ``` 注意cls必须在constructor构造函数中设置,而不能在sequlize实例中设置。 CLS的工作方式就像一个回调函数的本地线程存储。在sequlize中启用CLS后,需要在启动事务时设置transaction命名空间。在一个回调链中设置的变量时私有的,所以几个并发事务可以同时存在。 ``` javascript sequelize.transaction(function (t1) { namespace.get('transaction') === t1; }); sequelize.transaction(function (t2) { namespace.get('transaction') === t2; });
|
大多数情况下,你不用通过namespace.get(‘transaction’)直接访问命名空间,因为所有的查询都会自动查找事务的命名空间。
1 2 3 4
| sequelize.transaction(function (t1) { return User.create({ name: 'Alice' }); });
|
2.1.1 不受管理的事务(then-callback)
不受管理的事务需要你强制提交或回滚,如果不进行这些操作,事务会一直保持挂起状态直到超时。
启动一个不受管理的事务,同样是调用sequelize.transaction()方法,但不传递回调函数参数(仍然可以传递选项参数)。然后可以在其返回的promisethen方法中手工控制事务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| return sequelize.transaction().then(function (t) { return User.create({ firstName: 'Homer', lastName: 'Simpson' }, {transaction: t}).then(function (user) { return user.addSibling({ firstName: 'Lisa', lastName: 'Simpson' }, {transaction: t}); }).then(function () { return t.commit(); }).catch(function (err) { return t.rollback(); }); });
|