├── _config.yml ├── CHANGELOG.md ├── legacy.md ├── Readme.md ├── raw-queries.md ├── getting-started.md ├── upgrade-to-v4.md ├── scopes.md ├── transactions.md ├── usage.md ├── instances.md ├── hooks.md ├── querying.md ├── migrations.md ├── models-usage.md ├── models-definition.md └── associations.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新记录 2 | 3 | ## 2017-11-23 4 | * 同步官方最新文档版本 4.22.12 5 | 6 | ## 2017-10-19 7 | * 修改部分方法名使用原文 8 | 9 | ## 2017-10-16 10 | * 文档翻译完成 11 | 12 | 13 | ## 2017-10-12 14 | 15 | ### 更新 16 | * 同步官方最新文档版本 4.13.8 17 | * 翻译 getting-started.md 18 | * 翻译 models-definition.md * 翻译 models-usage.md * 翻译 querying.md 19 | 20 | ## 2017-07-14 21 | 22 | ### 新增 23 | * 增加来自 v4 的原文文档 24 | * 增加 README 文档 25 | * 增加 CHANGELOG 文档 -------------------------------------------------------------------------------- /legacy.md: -------------------------------------------------------------------------------- 1 | # Working with legacy tables - 使用遗留表 2 | 3 | 虽然 Sequelize 自认为可以开箱即用, 但是如果你要使用应用之前遗留的资产和凭据,仅需要做一点微不足道的设置即可。 4 | 5 | ## 表 6 | ```js 7 | sequelize.define('user', { 8 | 9 | }, { 10 | tableName: 'users' 11 | }); 12 | ``` 13 | 14 | ## 字段 15 | ```js 16 | sequelize.define('modelName', { 17 | userId: { 18 | type: Sequelize.INTEGER, 19 | field: 'user_id' 20 | } 21 | }); 22 | ``` 23 | 24 | ## 主键 25 | 26 | Sequelize将假设您的表默认具有`id`主键属性。 27 | 28 | 要定义你自己的主键: 29 | 30 | ```js 31 | sequelize.define('collection', { 32 | uid: { 33 | type: Sequelize.INTEGER, 34 | primaryKey: true, 35 | autoIncrement: true // Automatically gets converted to SERIAL for postgres 36 | } 37 | }); 38 | 39 | sequelize.define('collection', { 40 | uuid: { 41 | type: Sequelize.UUID, 42 | primaryKey: true 43 | } 44 | }); 45 | ``` 46 | 47 | 如果你的模型根本没有主键,你可以使用 `Model.removeAttribute('id');` 48 | 49 | ## 外键 50 | ```js 51 | // 1:1 52 | Organization.belongsTo(User, {foreignKey: 'owner_id'}); 53 | User.hasOne(Organization, {foreignKey: 'owner_id'}); 54 | 55 | // 1:M 56 | Project.hasMany(Task, {foreignKey: 'tasks_pk'}); 57 | Task.belongsTo(Project, {foreignKey: 'tasks_pk'}); 58 | 59 | // N:M 60 | User.hasMany(Role, {through: 'user_has_roles', foreignKey: 'user_role_user_id'}); 61 | Role.hasMany(User, {through: 'user_has_roles', foreignKey: 'roles_identifier'}); 62 | ``` 63 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Sequelize Docs 中文版 2 | 3 | ![](http://docs.sequelizejs.com/manual/asset/logo-small.png) 4 | 5 | [![Travis build](https://img.shields.io/travis/sequelize/sequelize/master.svg?style=flat-square)](https://travis-ci.org/sequelize/sequelize) 6 | [![npm](https://img.shields.io/npm/dm/sequelize.svg?style=flat-square)](https://npmjs.org/package/sequelize) 7 | [![npm](https://img.shields.io/npm/v/sequelize.svg?style=flat-square)](https://github.com/sequelize/sequelize/releases) 8 | 9 | > 此项目同步自 [sequelize](https://github.com/sequelize) / [sequelize](https://github.com/sequelize/sequelize) 项目中的 docs. 除特殊情况, 将保持每月一次的同步频率. 10 | > 11 | > 更新日志请参阅: [CHANGELOG](CHANGELOG.md) 12 | 13 | Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, SQLite 和 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 读取和复制等功能. 14 | 15 | ## 文档 16 | 17 | - [Getting started - 入门](getting-started.md) 18 | - [Model definition - 模型定义](models-definition.md) 19 | - [Model usage - 模型使用](models-usage.md) 20 | - [Querying - 查询](querying.md) 21 | - [Instances - 实例](instances.md) 22 | - [Associations - 关联](associations.md) 23 | - [Transactions - 事务](transactions.md) 24 | - [Scopes - 作用域](scopes.md) 25 | - [Hooks - 钩子](hooks.md) 26 | - [Raw queries - 原始查询](raw-queries.md) 27 | - [Migrations - 迁移](migrations.md) 28 | - [Upgrade to V4 - 升级到 V4](upgrade-to-v4.md) 29 | - [Working with legacy tables - 使用遗留表](legacy.md) 30 | 31 | 32 | 33 | 34 | ## 使用示例 35 | 36 | [Basic usage - 基本用法](usage.md) 37 | 38 | ```js 39 | const Sequelize = require('sequelize'); 40 | const sequelize = new Sequelize('database', 'username', 'password', { 41 | host: 'localhost', 42 | dialect: 'mysql'|'sqlite'|'postgres'|'mssql', 43 | 44 | pool: { 45 | max: 5, 46 | min: 0, 47 | acquire: 30000, 48 | idle: 10000 49 | }, 50 | 51 | // 仅限 SQLite 52 | storage: 'path/to/database.sqlite', 53 | 54 | // 请参考 Querying - 查询 操作符 章节 55 | operatorsAliases: false 56 | }); 57 | 58 | const User = sequelize.define('user', { 59 | username: Sequelize.STRING, 60 | birthday: Sequelize.DATE 61 | }); 62 | 63 | sequelize.sync() 64 | .then(() => User.create({ 65 | username: 'janedoe', 66 | birthday: new Date(1980, 6, 20) 67 | })) 68 | .then(jane => { 69 | console.log(jane.toJSON()); 70 | }); 71 | ``` 72 | 73 | 请通过 [Getting started - 入门](getting-started.md) 来学习更多相关内容。 如果你想要学习 Sequelize API 请通过 [API Reference](http://docs.sequelizejs.com/identifiers) (英文)。 -------------------------------------------------------------------------------- /raw-queries.md: -------------------------------------------------------------------------------- 1 | # Raw queries - 原始查询 2 | 3 | 由于常常使用简单的方式来执行原始/已经准备好的SQL查询,所以可以使用 `sequelize.query` 函数。 4 | 5 | 默认情况下,函数将返回两个参数 - 一个结果数组,以及一个包含元数据(受影响的行等)的对象。 请注意,由于这是一个原始查询,所以元数据(属性名称等)是具体的方言。 某些方言返回元数据 "within" 结果对象(作为数组上的属性)。 但是,将永远返回两个参数,但对于MSSQL和MySQL,它将是对同一对象的两个引用。 6 | 7 | ```js 8 | sequelize.query("UPDATE users SET y = 42 WHERE x = 12").spread((results, metadata) => { 9 | // 结果将是一个空数组,元数据将包含受影响的行数。 10 | }) 11 | ``` 12 | 13 | 在不需要访问元数据的情况下,您可以传递一个查询类型来告诉后续如何格式化结果。 例如,对于一个简单的选择查询你可以做: 14 | 15 | ```js 16 | sequelize.query("SELECT * FROM `users`", { type: sequelize.QueryTypes.SELECT}) 17 | .then(users => { 18 | // 我们不需要在这里延伸,因为只有结果将返回给选择查询 19 | }) 20 | ``` 21 | 22 | 还有其他几种查询类型可用。 [详细了解来源](https://github.com/sequelize/sequelize/blob/master/lib/query-types.js) 23 | 24 | 第二种选择是模型。 如果传递模型,返回的数据将是该模型的实例。 25 | 26 | ```js 27 | // Callee 是模型定义。 这样您就可以轻松地将查询映射到预定义的模型 28 | sequelize.query('SELECT * FROM projects', { model: Projects }).then(projects => { 29 | // 每个记录现在将是Project的一个实例 30 | }) 31 | ``` 32 | 33 | ## 替换 34 | 35 | 查询中的替换可以通过两种不同的方式完成:使用命名参数(以`:`开头),或者由`?`表示的未命名参数。 替换在options对象中传递。 36 | 37 | * 如果传递一个数组, `?` 将按照它们在数组中出现的顺序被替换 38 | * 如果传递一个对象, `:key` 将替换为该对象的键。 如果对象包含在查询中找不到的键,则会抛出异常,反之亦然。 39 | 40 | ```js 41 | sequelize.query('SELECT * FROM projects WHERE status = ?', 42 | { replacements: ['active'], type: sequelize.QueryTypes.SELECT } 43 | ).then(projects => { 44 | console.log(projects) 45 | }) 46 | 47 | sequelize.query('SELECT * FROM projects WHERE status = :status ', 48 | { replacements: { status: 'active' }, type: sequelize.QueryTypes.SELECT } 49 | ).then(projects => { 50 | console.log(projects) 51 | }) 52 | ``` 53 | 54 | 数组替换将自动处理,以下查询将搜索状态与值数组匹配的项目。 55 | 56 | ```js 57 | sequelize.query('SELECT * FROM projects WHERE status IN(:status) ', 58 | { replacements: { status: ['active', 'inactive'] }, type: sequelize.QueryTypes.SELECT } 59 | ).then(projects => { 60 | console.log(projects) 61 | }) 62 | ``` 63 | 64 | 要使用通配符运算符 %,请将其附加到你的替换中。 以下查询与名称以“ben”开头的用户相匹配。 65 | 66 | ```js 67 | sequelize.query('SELECT * FROM users WHERE name LIKE :search_name ', 68 | { replacements: { search_name: 'ben%' }, type: sequelize.QueryTypes.SELECT } 69 | ).then(projects => { 70 | console.log(projects) 71 | }) 72 | ``` 73 | 74 | ## 绑定参数 75 | 76 | 绑定参数就像替换。 除非替换被转义并在查询发送到数据库之前通过后续插入到查询中,而将绑定参数发送到SQL查询文本之外的数据库。 查询可以具有绑定参数或替换。 77 | 78 | 只有SQLite和PostgreSQL支持绑定参数。 其他方言会将它们插入到SQL查询中,就像替换一样。 绑定参数由 `$1, $2, ... (numeric)` 或 `$key (alpha-numeric)` 引用。这是独立于方言。 79 | 80 | * 如果传递一个数组, `$1` 被绑定到数组中的第一个元素 (`bind[0]`)。 81 | * 如果传递一个对象, `$key` 绑定到 `object['key']`。 每个键必须以非数字字符开始。 `$1` 不是一个有效的键,即使 `object['1']` 存在。 82 | * 在这两种情况下 `$$` 可以用来转义一个 `$` 字符符号. 83 | 84 | 数组或对象必须包含所有绑定的值,或者Sequelize将抛出异常。 这甚至适用于数据库可能忽略绑定参数的情况。 85 | 86 | 数据库可能会增加进一步的限制。 绑定参数不能是SQL关键字,也不能是表或列名。 引用的文本或数据也忽略它们。 在PostgreSQL中,如果不能从上下文 `$1::varchar` 推断类型,那么也可能需要对其进行类型转换。 87 | 88 | ```js 89 | sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $1', 90 | { bind: ['active'], type: sequelize.QueryTypes.SELECT } 91 | ).then(projects => { 92 | console.log(projects) 93 | }) 94 | 95 | sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $status', 96 | { bind: { status: 'active' }, type: sequelize.QueryTypes.SELECT } 97 | ).then(projects => { 98 | console.log(projects) 99 | }) 100 | ``` 101 | 102 | -------------------------------------------------------------------------------- /getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started - 入门 2 | 3 | ## 安装 4 | 5 | Sequelize 可通过 NPM 和 Yarn 获得。 6 | 7 | ```bash 8 | // 使用 NPM 9 | $ npm install --save sequelize 10 | 11 | # 还有以下之一: 12 | $ npm install --save pg pg-hstore 13 | $ npm install --save mysql2 14 | $ npm install --save sqlite3 15 | $ npm install --save tedious // MSSQL 16 | 17 | // 使用 Yarn 18 | $ yarn add sequelize 19 | 20 | # 还有以下之一: 21 | $ yarn add pg pg-hstore 22 | $ yarn add mysql2 23 | $ yarn add sqlite3 24 | $ yarn add tedious // MSSQL 25 | ``` 26 | 27 | ## 建立连接 28 | 29 | Sequelize将在初始化时设置连接池,所以如果从单个进程连接到数据库,你最好每个数据库只创建一个实例。 如果要从多个进程连接到数据库,则必须为每个进程创建一个实例,但每个实例应具有“最大连接池大小除以实例数”的最大连接池大小。 因此,如果您希望最大连接池大小为90,并且有3个工作进程,则每个进程的实例应具有30的最大连接池大小。 30 | 31 | ```js 32 | const Sequelize = require('sequelize'); 33 | const sequelize = new Sequelize('database', 'username', 'password', { 34 | host: 'localhost', 35 | dialect: 'mysql'|'sqlite'|'postgres'|'mssql', 36 | 37 | pool: { 38 | max: 5, 39 | min: 0, 40 | acquire: 30000, 41 | idle: 10000 42 | }, 43 | 44 | // 仅限 SQLite 45 | storage: 'path/to/database.sqlite' 46 | }); 47 | 48 | // 或者你可以简单地使用 uri 连接 49 | const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname'); 50 | ``` 51 | 52 | Sequelize 构造函数可以通过 [API reference](/class/lib/sequelize.js~Sequelize.html) 获得一整套可用的参数。 53 | 54 | ## 测试连接 55 | 56 | 您可以使用 `.authenticate()` 函数来测试连接。 57 | 58 | ```js 59 | sequelize 60 | .authenticate() 61 | .then(() => { 62 | console.log('Connection has been established successfully.'); 63 | }) 64 | .catch(err => { 65 | console.error('Unable to connect to the database:', err); 66 | }); 67 | ``` 68 | 69 | ## 你的第一个模型 70 | 71 | 模型使用 `sequelize.define('name', {attributes}, {options})` 来定义. 72 | 73 | ```js 74 | const User = sequelize.define('user', { 75 | firstName: { 76 | type: Sequelize.STRING 77 | }, 78 | lastName: { 79 | type: Sequelize.STRING 80 | } 81 | }); 82 | 83 | // force: true 如果表已经存在,将会丢弃表 84 | User.sync({force: true}).then(() => { 85 | // 表已创建 86 | return User.create({ 87 | firstName: 'John', 88 | lastName: 'Hancock' 89 | }); 90 | }); 91 | ``` 92 | 93 | 您可以在 [Model API reference](/class/lib/model.js~Model.html) 中阅读更多关于创建模型的信息。 94 | 95 | ## 你的第一个查询 96 | 97 | ```js 98 | User.findAll().then(users => { 99 | console.log(users) 100 | }) 101 | ``` 102 | 103 | 您可以在 [Data retrieval](/manual/tutorial/models-usage.html#data-retrieval-finders) 上查看更多关于模型的查找器功能,如 `.findAll()` 。或者在 [Querying](/manual/tutorial/querying.html) 上查看如何执行特定查询,如 `WHERE` 和 `JSONB` 。 104 | 105 | ### 应用全局的模型参数 106 | 107 | Sequelize 构造函数使用 `define` 参数,该参数将用作所有定义模型的默认参数。 108 | 109 | ```js 110 | const sequelize = new Sequelize('connectionUri', { 111 | define: { 112 | timestamps: false // 默认为 true 113 | } 114 | }); 115 | 116 | const User = sequelize.define('user', {}); // 时间戳默认为 false 117 | const Post = sequelize.define('post', {}, { 118 | timestamps: true // 时间戳此时为 false 119 | }); 120 | ``` 121 | 122 | ## Promise 123 | 124 | Sequelize 使用 [Bluebird](http://bluebirdjs.com) promise 来控制异步控制流程。 125 | 126 | **注意:** _Sequelize 使用 Bluebird 实例的独立副本。如果你想设置任何 Bluebird 特定的参数可以通过使用 `Sequelize.Promise` 来访问它。_ 127 | 128 | 如果你不熟悉 promise 是如何工作的,别担心,你可以阅读一下 [这里](http://bluebirdjs.com/docs/why-promises.html)。 129 | 130 | 基本上,一个 promise 代表了某个时候会出现的值 - 这意味着“我保证你会在某个时候给你一个结果或一个错误”。 131 | 132 | ```js 133 | // 不要这样做 134 | user = User.findOne() 135 | 136 | console.log(user.get('firstName')); 137 | ``` 138 | 139 | _这将永远不可用!_这是因为`user`是 promise 对象,而不是数据库中的数据行。 正确的方法是: 140 | 141 | ```js 142 | User.findOne().then(user => { 143 | console.log(user.get('firstName')); 144 | }); 145 | ``` 146 | 147 | 当您的环境或解释器支持 [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) 时,这将可用,但只能在 [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 方法体中: 148 | 149 | ```js 150 | user = await User.findOne() 151 | 152 | console.log(user.get('firstName')); 153 | ``` 154 | 155 | 一旦知道了什么是 promise 以及它们的工作原理,请使用 [bluebird API reference](http://bluebirdjs.com/docs/api-reference.html) 作为转移工具。 尤其是,你可能会使用很多 [`.all`](http://bluebirdjs.com/docs/api/promise.all.html) 。 156 | -------------------------------------------------------------------------------- /upgrade-to-v4.md: -------------------------------------------------------------------------------- 1 | # Upgrade to V4 - 升级到 V4 2 | 3 | Sequelize V4 是一个重要版本,它引入了新的功能和突破性的变化。 大量的 sequelize 代码库已用 ES2015 功能重构。 以下指南列出了从 v3 升级到 v4 的一些更改。查看 [修改日志](https://github.com/sequelize/sequelize/blob/b49f936e9aa316cf4a13bade76585acf4d5d8b04/changelog.md) 查看全部详细列表。 4 | 5 | ### 突破性变化 6 | 7 | - Node 版本: 要使用新的 ES2015 功能,我们现在至少需要 Node4。从现在开始,我们将支持所有当前的LTS版本的Node。 8 | - 计数器缓存插件以及因此关联的计数器缓存选项已被删除。 使用 `afterCreate` 和 `afterDelete` 钩子可以实现相同的行为。 9 | - 删除了MariaDB方言。 这只是围绕 MySQL 的一个浅层包装,所以使用 `dialect:'mysql` 而不是进一步的改变。 10 | - 删除默认的 `REPEATABLE_READ` 事务隔离。 隔离级别现在默认为数据库的级别。 在启动事务时明确地传递所需的隔离级别。 11 | - 删除了对 `pool: false` 的支持。要使用单个连接,请将 `pool.max` 设置为1。 12 | - (MySQL)当数字太大时,BIGINT 现在被转换为字符串。 13 | - 删除了对referencesKey的支持,使用了一个引用对象。 14 | 15 | ```js 16 | references: { 17 | key: '', 18 | model: '' 19 | } 20 | ``` 21 | 22 | - `classMethods` 和 `instanceMethods` 已被移除。 23 | 24 | 以前: 25 | 26 | ```js 27 | const Model = sequelize.define('Model', { 28 | ... 29 | }, { 30 | classMethods: { 31 | associate: function (model) {...} 32 | }, 33 | instanceMethods: { 34 | someMethod: function () { ...} 35 | } 36 | }); 37 | ``` 38 | 39 | 现在: 40 | 41 | ```js 42 | const Model = sequelize.define('Model', { 43 | ... 44 | }); 45 | 46 | // 类方法 47 | Model.associate = function (models) { 48 | ...associate the models 49 | }; 50 | 51 | // 实例方法 52 | Model.prototype.someMethod = function () {..} 53 | ``` 54 | 55 | - `Model.Instance` 和 `instance.Model` 已被移除。要从一个实例访问模型,只需使用 [`instance.constructor`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor)。 示例类 (`Model.Instance`) 现在是模型本身。 56 | - Sequelize 现在使用一个 bluebird 库的独立副本. 57 | 58 | - sequelize返回的 promise 现在是 `Sequelize.Promise` 而不是 bluebird 的全局 `Promise` 实例。 59 | - CLS 补丁不会影响 bluebird 的全局 promise。当与 `Promise.all` 和其他 bluebird 方法一起使用时,事务不会自动传递给方法。明确地修补 bluebird 实例,可以让 CLS 能够使用 bluebird 方法。 60 | 61 | ```bash 62 | $ npm install --save cls-bluebird 63 | ``` 64 | 65 | ```js 66 | const Promise = require('bluebird'); 67 | const Sequelize = require('sequelize'); 68 | const cls = require('continuation-local-storage'); 69 | const ns = cls.createNamespace('transaction-namespace'); 70 | const clsBluebird = require('cls-bluebird'); 71 | clsBluebird(ns, Promise); 72 | Sequelize.useCLS(ns); 73 | ``` 74 | - `Sequelize.Validator` 现在是 `validator` 库的独立副本 75 | - `DataTypes.DECIMAL` 对于 MySQL 和 Postgres 返回的是字符串. 76 | - `DataTypes.DATE` 现在使用 `DATETIMEOFFSET` 而不是 `DATETIME2` sql数据类型,以防MSSQL记录时区。要将现有的 `DATETIME2` 列迁移到 `DATETIMEOFFSET` 中, 查看 [#7201](https://github.com/sequelize/sequelize/pull/7201#issuecomment-278899803). 77 | - `options.order` 现在只接受数组类型或 Sequelize 方法的值。 原限支持的字符串值(即`{order:'name DESC'}`)已被弃用。 78 | - 使用 `BelongsToMany` 关系 `add / set / create` 设置器现在通过将它们传递为 `options.through` 来设置属性(以前的第二个参数被用作通过属性,现在它被认为是 `through` 作为子选项的选项)。 79 | 80 | 以前: 81 | 82 | ```js 83 | user.addProject(project, { status: 'started' }) 84 | ``` 85 | 86 | 现在: 87 | 88 | ```js 89 | user.addProject(project, { through: { status: 'started' }}) 90 | ``` 91 | 92 | - `DATEONLY` 现在以 `YYYY-MM-DD` 格式而不是 `Date` 类型返回字符串 93 | - `Model.validate` 实例方法默认运行验证钩子。以前你需要传递 `{ hooks: true }`. 您可以通过传递 `{ hooks: false }` 来覆盖此行为。 94 | - 当验证失败时,来自 `Model.validate` 实例方法的结果将被拒绝。 验证成功后才能实现。 95 | - 原始参数 where, order 和 group 比如 `where: { $raw: '..', order: [{ raw: '..' }], group: [{ raw: '..' }] }` 删除以防止SQL注入攻击。 96 | - `Sequelize.Utils` 不再是公共API的一部分,使用它自己承担风险。 97 | - `Hooks` 现在应返回 promise。 不支持回调。 98 | 99 | ### 新功能 100 | - `sequelize.sync({ alter: true })` 的初始版本已添加,并使用 `ALTER TABLE` 命令来同步表。 [迁移](http://docs.sequelizejs.com/manual/tutorial/migrations.html) 仍然是首选,应在生产中使用。 101 | - 现在支持添加和删除数据库约束。 现有的 primary,foreignKey 和其他约束现在可以使用迁移来添加/删除 - [查看更多](http://docs.sequelizejs.com/manual/tutorial/migrations.html#addconstraint-tablename-attributes-options-). 102 | - 实例(数据库行)现在是模型的实例,而不是单独类的实例。这意味着你可以替换`User.build()` 用 `new User()` 和 `sequelize.define(attributes, options)` 用 103 | 104 | ```js 105 | class User extends Sequelize.Model {} 106 | User.init(attributes, options) 107 | ``` 108 | 109 | 然后,您可以直接在类中定义自定义方法,类方法和 getter / setter。 110 | 这也使得有更多的使用模式,例如用 [装饰器](https://www.npmjs.com/package/sequelize-decorators). 111 | - 增加了 `DEBUG` 支持。 现在可以使用 `DEBUG = sequelize * node app.js` 为所有 sequelize 操作启用日志记录。 要过滤记录的查询,请使用 `DEBUG=sequelize:sql:mssql sequelize:connection*` 来记录生成的SQL查询,连接信息等。 112 | - `SQLite` 添加了 `JSON` 数据类型支持。 113 | - `UPSERT` 现在使用 `MERGE` 语句支持 `MSSQL`。 114 | - 事务现在完全支持 `MSSQL`。 115 | - `MSSQL` 方言现在支持过滤的索引。 116 | 117 | ```js 118 | queryInterface.addIndex( 119 | 'Person', 120 | ['firstname', 'lastname'], 121 | { 122 | where: { 123 | lastname: { 124 | $ne: null 125 | } 126 | } 127 | } 128 | ) 129 | ``` 130 | -------------------------------------------------------------------------------- /scopes.md: -------------------------------------------------------------------------------- 1 | # Scopes - 作用域 2 | 3 | 作用域允许你定义常用查询,以便以后轻松使用。 作用域可以包括与常规查找器 `where`, `include`, `limit` 等所有相同的属性。 4 | 5 | ## 定义 6 | 7 | 作用域在模型定义中定义,可以是finder对象或返回finder对象的函数,除了默认作用域,该作用于只能是一个对象: 8 | 9 | ```js 10 | const Project = sequelize.define('project', { 11 | // 属性 12 | }, { 13 | defaultScope: { 14 | where: { 15 | active: true 16 | } 17 | }, 18 | scopes: { 19 | deleted: { 20 | where: { 21 | deleted: true 22 | } 23 | }, 24 | activeUsers: { 25 | include: [ 26 | { model: User, where: { active: true }} 27 | ] 28 | }, 29 | random: function () { 30 | return { 31 | where: { 32 | someNumber: Math.random() 33 | } 34 | } 35 | }, 36 | accessLevel: function (value) { 37 | return { 38 | where: { 39 | accessLevel: { 40 | [Op.gte]: value 41 | } 42 | } 43 | } 44 | } 45 | } 46 | }); 47 | ``` 48 | 49 | 通过调用 `addScope` 定义模型后,还可以添加作用域。 这对于具有包含的作用域特别有用,其中在定义其他模型时可能不会定义 include 中的模型。 50 | 51 | 始终应用默认作用域。 这意味着,通过上面的模型定义,`Project.findAll()` 将创建以下查询: 52 | 53 | ```sql 54 | SELECT * FROM projects WHERE active = true 55 | ``` 56 | 57 | 可以通过调用 `.unscoped()`, `.scope(null)` 或通过调用另一个作用域来删除默认作用域: 58 | 59 | ```js 60 | Project.scope('deleted').findAll(); // 删除默认作用域 61 | ``` 62 | ```sql 63 | SELECT * FROM projects WHERE deleted = true 64 | ``` 65 | 66 | 还可以在作用域定义中包含作用域模型。 这让你避免重复 `include`,`attributes` 或 `where` 定义。 67 | 68 | 使用上面的例子,并在包含的用户模型中调用 `active` 作用域(而不是直接在该 include 对象中指定条件): 69 | 70 | ```js 71 | activeUsers: { 72 | include: [ 73 | { model: User.scope('active')} 74 | ] 75 | } 76 | ``` 77 | 78 | ## 使用 79 | 80 | 通过在模型定义上调用 `.scope` 来应用作用域,传递一个或多个作用域的名称。 `.scope` 返回一个全功能的模型实例,它具有所有常规的方法:`.findAll`,`.update`,`.count`,`.destroy`等等。你可以保存这个模型实例并稍后再次使用: 81 | 82 | ```js 83 | const DeletedProjects = Project.scope('deleted'); 84 | 85 | DeletedProjects.findAll(); 86 | // 过一段时间 87 | 88 | // 让我们再次寻找被删除的项目! 89 | DeletedProjects.findAll(); 90 | ``` 91 | 92 | 作用域适用于 `.find`, `.findAll`, `.count`, `.update`, `.increment` 和 `.destroy`. 93 | 94 | 可以通过两种方式调用作为函数的作用域。 如果作用域没有任何参数,它可以正常调用。 如果作用域采用参数,则传递一个对象: 95 | 96 | ```js 97 | Project.scope('random', { method: ['accessLevel', 19]}).findAll(); 98 | ``` 99 | 100 | ```sql 101 | SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19 102 | ``` 103 | 104 | ## 合并 105 | 106 | 通过将作用域数组传递到 `.scope` 或通过将作用域作为连续参数传递,可以同时应用多个作用域。 107 | 108 | ```js 109 | // 这两个是等价的 110 | Project.scope('deleted', 'activeUsers').findAll(); 111 | Project.scope(['deleted', 'activeUsers']).findAll(); 112 | ``` 113 | 114 | ```sql 115 | SELECT * FROM projects 116 | INNER JOIN users ON projects.userId = users.id 117 | AND users.active = true 118 | ``` 119 | 120 | 如果要将其他作用域与默认作用域一起应用,请将键 `defaultScope` 传递给 `.scope`: 121 | 122 | ```js 123 | Project.scope('defaultScope', 'deleted').findAll(); 124 | ``` 125 | 126 | ```sql 127 | SELECT * FROM projects WHERE active = true AND deleted = true 128 | ``` 129 | 130 | 当调用多个作用域时,后续作用域的键将覆盖以前的作用域(类似于 [_.assign](https://lodash.com/docs#assign) )。 考虑两个作用域: 131 | 132 | ```js 133 | { 134 | scope1: { 135 | where: { 136 | firstName: 'bob', 137 | age: { 138 | [Op.gt]: 20 139 | } 140 | }, 141 | limit: 2 142 | }, 143 | scope2: { 144 | where: { 145 | age: { 146 | [Op.gt]: 30 147 | } 148 | }, 149 | limit: 10 150 | } 151 | } 152 | ``` 153 | 154 | 调用 `.scope('scope1', 'scope2')` 将产生以下查询 155 | 156 | ```sql 157 | WHERE firstName = 'bob' AND age > 30 LIMIT 10 158 | ``` 159 | 160 | 注意 `scope2` 覆盖 `limit` 和 `age`,而 `firstName` 被保留。 `limit`,`offset`,`order`,`paranoid`,`lock`和`raw`被覆盖,而`where`和`include`被浅层合并。 这意味着相同的键在同一个模型的对象以及随后的包含都将相互覆盖。 161 | 162 | 当将查找对象直接传递到作用域模型上的 findAll 时,适用相同的合并逻辑: 163 | 164 | ```js 165 | Project.scope('deleted').findAll({ 166 | where: { 167 | firstName: 'john' 168 | } 169 | }) 170 | ``` 171 | 172 | ```sql 173 | WHERE deleted = true AND firstName = 'john' 174 | ``` 175 | 176 | 这里的 `deleted` 作用域与 finder 合并。 如果我们要将 `where: { firstName: 'john', deleted: false }` 传递给 finder,那么 `deleted` 作用域将被覆盖。 177 | 178 | ## 关联 179 | 180 | Sequelize 与关联有两个不同但相关的作用域概念。 差异是微妙但重要的: 181 | 182 | * **关联作用域** 允许您在获取和设置关联时指定默认属性 - 在实现多态关联时很有用。 当使用`get`,`set`,`add`和`create`相关联的模型函数时,这个作用域仅在两个模型之间的关联上被调用 183 | * **关联模型上的作用域** 允许您在获取关联时应用默认和其他作用域,并允许您在创建关联时传递作用域模型。 这些作用域都适用于模型上的常规查找和通过关联查找。 184 | 185 | 举个例子,思考模型Post和Comment。 注释与其他几个模型(图像,视频等)相关联,注释和其他模型之间的关联是多态的,这意味着除了外键 `commentable_id` 之外,注释还存储一个`commentable`列。 186 | 187 | 可以使用 _association scope_ 来实现多态关联: 188 | 189 | ```js 190 | this.Post.hasMany(this.Comment, { 191 | foreignKey: 'commentable_id', 192 | scope: { 193 | commentable: 'post' 194 | } 195 | }); 196 | ``` 197 | 198 | 当调用 `post.getComments()` 时,这将自动添加 `WHERE commentable = 'post'`。 类似地,当向帖子添加新的注释时,`commentable` 会自动设置为 `'post'`。 关联作用域是为了存活于后台,没有程序员不必担心 - 它不能被禁用。 有关更完整的多态性示例,请参阅 [关联作用域](/manual/tutorial/associations.html#scopes) 199 | 200 | 那么考虑那个Post的默认作用域只显示活动的帖子:`where: { active: true }`。 该作用域存在于相关联的模型(Post)上,而不是像`commentable` 作用域那样在关联上。 就像在调用`Post.findAll()` 时一样应用默认作用域,当调用 `User.getPosts()` 时,它也会被应用 - 这只会返回该用户的活动帖子。 201 | 202 | 要禁用默认作用域,将 `scope: null` 传递给 getter: `User.getPosts({ scope: null })`。 同样,如果要应用其他作用域,请像这样: 203 | 204 | ```js 205 | User.getPosts({ scope: ['scope1', 'scope2']}); 206 | ``` 207 | 208 | 如果要为关联模型上的作用域创建快捷方式,可以将作用域模型传递给关联。 考虑一个快捷方式来获取用户所有已删除的帖子: 209 | 210 | ```js 211 | const Post = sequelize.define('post', attributes, { 212 | defaultScope: { 213 | where: { 214 | active: true 215 | } 216 | }, 217 | scopes: { 218 | deleted: { 219 | where: { 220 | deleted: true 221 | } 222 | } 223 | } 224 | }); 225 | 226 | User.hasMany(Post); // 常规 getPosts 关联 227 | User.hasMany(Post.scope('deleted'), { as: 'deletedPosts' }); 228 | 229 | ``` 230 | 231 | ```js 232 | User.getPosts(); // WHERE active = true 233 | User.getDeletedPosts(); // WHERE deleted = true 234 | ``` 235 | -------------------------------------------------------------------------------- /transactions.md: -------------------------------------------------------------------------------- 1 | # Transactions - 事务 2 | 3 | Sequelize 支持两种使用事务的方法: 4 | 5 | * 一个将根据 promise 链的结果自动提交或回滚事务,(如果启用)用回调将该事务传递给所有调用 6 | * 而另一个 leave committing,回滚并将事务传递给用户。 7 | 8 | 主要区别在于托管事务使用一个回调,对非托管事务而言期望 promise 返回一个 promise 的结果。 9 | 10 | ## 托管事务(auto-callback) 11 | 12 | 托管事务自动处理提交或回滚事务。你可以通过将回调传递给 `sequelize.transaction` 来启动托管事务。 13 | 14 | 注意回传传递给 `transaction` 的回调是否是一个 promise 链,并且没有明确地调用`t.commit()`或 `t.rollback()`。 如果返回链中的所有 promise 都已成功解决,则事务被提交。 如果一个或几个 promise 被拒绝,事务将回滚。 15 | 16 | ```js 17 | return sequelize.transaction(function (t) { 18 | 19 | // 在这里链接您的所有查询。 确保你返回他们。 20 | return User.create({ 21 | firstName: 'Abraham', 22 | lastName: 'Lincoln' 23 | }, {transaction: t}).then(function (user) { 24 | return user.setShooter({ 25 | firstName: 'John', 26 | lastName: 'Boothe' 27 | }, {transaction: t}); 28 | }); 29 | 30 | }).then(function (result) { 31 | // 事务已被提交 32 | // result 是 promise 链返回到事务回调的结果 33 | }).catch(function (err) { 34 | // 事务已被回滚 35 | // err 是拒绝 promise 链返回到事务回调的错误 36 | }); 37 | ``` 38 | 39 | ### 抛出错误到回滚 40 | 41 | 使用托管事务时,你应该 _**永不**_ 手动提交或回滚事务。 如果所有查询都成功,但您仍然希望回滚事务(例如因为验证失败),则应该抛出一个错误来断开和拒绝链接: 42 | 43 | ```js 44 | return sequelize.transaction(function (t) { 45 | return User.create({ 46 | firstName: 'Abraham', 47 | lastName: 'Lincoln' 48 | }, {transaction: t}).then(function (user) { 49 | // 查询成功,但我们仍然想回滚! 50 | throw new Error(); 51 | }); 52 | }); 53 | ``` 54 | 55 | ### 自动将事务传递给所有查询 56 | 57 | 在上面的例子中,事务仍然是手动传递的,通过传递 `{transaction:t}` 作为第二个参数。 要自动将事务传递给所有查询,您必须安装 [continuation local storage](https://github.com/othiym23/node-continuation-local-storage) (CLS) 模块,并在您自己的代码中实例化一个命名空间: 58 | 59 | ```js 60 | const cls = require('continuation-local-storage'), 61 | namespace = cls.createNamespace('my-very-own-namespace'); 62 | ``` 63 | 64 | 要启用CLS,您必须通过使用sequelize构造函数的静态方法来告诉Sequelize要使用的命名空间: 65 | 66 | ```js 67 | const Sequelize = require('sequelize'); 68 | Sequelize.useCLS(namespace); 69 | 70 | new Sequelize(....); 71 | ``` 72 | 73 | 请注意, `useCLS()` 方法在 *构造函数* 上,而不是在 sequelize 的实例上。 这意味着所有实例将共享相同的命名空间,并且 CLS 是全部或全无方式 - 你不能仅在某些实例中启用它。 74 | 75 | CLS 的工作方式就像一个用于回调的本地线程存储。 这在实践中意味着不同的回调链可以通过使用 CLS 命名空间来访问局部变量。 当启用 CLS 时,创建新事务时,Sequelize 将在命名空间上设置 `transaction` 属性。 由于回调链中设置的变量对该链是私有的,因此可以同时存在多个并发事务: 76 | 77 | ```js 78 | sequelize.transaction(function (t1) { 79 | namespace.get('transaction') === t1; // true 80 | }); 81 | 82 | sequelize.transaction(function (t2) { 83 | namespace.get('transaction') === t2; // true 84 | }); 85 | ``` 86 | 87 | 在大多数情况下,你不需要直接访问 `namespace.get('transaction')`,因为所有查询都将自动在命名空间中查找事务: 88 | 89 | ```js 90 | sequelize.transaction(function (t1) { 91 | // 启用 CLS 后,将在事务中创建用户 92 | return User.create({ name: 'Alice' }); 93 | }); 94 | ``` 95 | 96 | 在使用 `Sequelize.useCLS()` 后,从 sequelize 返回的所有 promise 将被修补以维护 CLS 上下文。 CLS 是一个复杂的课题 - [cls-bluebird](https://www.npmjs.com/package/cls-bluebird)的文档中有更多细节,用于使 bluebird promise 的补丁与CLS一起工作。 97 | 98 | ## 并行/部分事务 99 | 100 | 你可以在一系列查询中执行并发事务,或者将某些事务从任何事务中排除。 使用 `{transaction: }` 选项来控制查询所属的事务: 101 | 102 | ### 不启用CLS 103 | 104 | ```js 105 | sequelize.transaction(function (t1) { 106 | return sequelize.transaction(function (t2) { 107 | // 启用CLS,这里的查询将默认使用 t2 108 | // 通过 `transaction` 选项来定义/更改它们所属的事务。 109 | return Promise.all([ 110 | User.create({ name: 'Bob' }, { transaction: null }), 111 | User.create({ name: 'Mallory' }, { transaction: t1 }), 112 | User.create({ name: 'John' }) // 这将默认为 t2 113 | ]); 114 | }); 115 | }); 116 | ``` 117 | 118 | ## 隔离等级 119 | 120 | 启动事务时可能使用的隔离等级: 121 | 122 | ```js 123 | Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED" 124 | Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED" 125 | Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ" 126 | Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE" 127 | ``` 128 | 129 | 默认情况下,sequelize 使用数据库的隔离级别。 如果要使用不同的隔离级别,请传入所需级别作为第一个参数: 130 | 131 | ```js 132 | return sequelize.transaction({ 133 | isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE 134 | }, function (t) { 135 | 136 | // 你的事务 137 | 138 | }); 139 | ``` 140 | 141 | 注意: 在MSSQL的情况下,SET ISOLATION LEVEL 查询不被记录, 指定的 isolationLevel 直接传递到tedious 142 | 143 | ## 非托管事务(then-callback) 144 | 145 | 非托管事务强制您手动回滚或提交交易。 如果不这样做,事务将挂起,直到超时。 要启动非托管事务,请调用 `sequelize.transaction()` 而不用 callback(你仍然可以传递一个选项对象),并在返回的 promise 上调用 `then`。 请注意,`commit()` 和 `rollback()` 返回一个 promise。 146 | 147 | ```js 148 | return sequelize.transaction().then(function (t) { 149 | return User.create({ 150 | firstName: 'Bart', 151 | lastName: 'Simpson' 152 | }, {transaction: t}).then(function (user) { 153 | return user.addSibling({ 154 | firstName: 'Lisa', 155 | lastName: 'Simpson' 156 | }, {transaction: t}); 157 | }).then(function () { 158 | return t.commit(); 159 | }).catch(function (err) { 160 | return t.rollback(); 161 | }); 162 | }); 163 | ``` 164 | 165 | ## 参数 166 | 167 | 可以使用options对象作为第一个参数来调用`transaction`方法,这允许配置事务。 168 | 169 | ```js 170 | return sequelize.transaction({ /* options */ }); 171 | ``` 172 | 173 | 以下选项(使用默认值)可用: 174 | 175 | ```js 176 | { 177 | autocommit: true, 178 | isolationLevel: 'REPEATABLE_READ', 179 | deferrable: 'NOT DEFERRABLE' // postgres 的默认设置 180 | } 181 | ``` 182 | 183 | 在为 Sequelize 实例或每个局部事务初始化时,`isolationLevel`可以全局设置: 184 | 185 | ```js 186 | // 全局 187 | new Sequelize('db', 'user', 'pw', { 188 | isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE 189 | }); 190 | 191 | // 局部 192 | sequelize.transaction({ 193 | isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE 194 | }); 195 | ``` 196 | 197 | `deferrable` 选项在事务开始后触发一个额外的查询,可选地将约束检查设置为延迟或立即。 请注意,这仅在PostgreSQL中受支持。 198 | 199 | ```js 200 | sequelize.transaction({ 201 | // 推迟所有约束: 202 | deferrable: Sequelize.Deferrable.SET_DEFERRED, 203 | 204 | // 推迟具体约束: 205 | deferrable: Sequelize.Deferrable.SET_DEFERRED(['some_constraint']), 206 | 207 | // 不推迟约束: 208 | deferrable: Sequelize.Deferrable.SET_IMMEDIATE 209 | }) 210 | ``` 211 | 212 | ## 使用其他 Sequelize 方法 213 | 214 | `transaction` 选项与其他大多数选项一起使用,通常是方法的第一个参数。 215 | 对于取值的方法,如 `.create`, `.update()`, `.updateAttributes()` 等。应该传递给第二个参数的选项。 216 | 如果不确定,请参阅API文档中的用于确定签名的方法。 217 | -------------------------------------------------------------------------------- /usage.md: -------------------------------------------------------------------------------- 1 | ## Basic usage - 基本用法 2 | 3 | 在开始之前,你首先必须创建一个 Sequelize 的实例。 像下面这样: 4 | 5 | ```js 6 | const sequelize = new Sequelize('database', 'username'[, 'password']) 7 | ``` 8 | 9 | 这将会保存要传递的数据库凭据并提供所有进一步的方法。此外,你还可以指定非默认的主机或端口: 10 | 11 | ```js 12 | const sequelize = new Sequelize('database', 'username', 'password', { 13 | host: "my.server.tld", 14 | port: 12345 15 | }) 16 | ``` 17 | 18 | 如果你没有密码: 19 | 20 | ```js 21 | const sequelize = new Sequelize('database', 'username') 22 | // 或 23 | const sequelize = new Sequelize('database', 'username', null) 24 | ``` 25 | 26 | 你也可以使用连接字符串: 27 | 28 | ```js 29 | const sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname', { 30 | // 更多选项请看下一节 31 | }) 32 | ``` 33 | 34 | ## 选项 35 | 36 | 除了主机和端口,Sequelize 还提供了一大堆选项。它们在这: 37 | 38 | ```js 39 | const sequelize = new Sequelize('database', 'username', 'password', { 40 | // 自定义主机; 默认值: localhost 41 | host: 'my.server.tld', 42 |   43 | // 自定义端口; 默认值: 3306 44 | port: 12345, 45 |   46 | // 自定义协议 47 | // - 默认值: 'tcp' 48 | // - 版本: v1.5.0 49 | // - 仅限 postgres, 用于 heroku 50 | protocol: null, 51 |   52 | // 禁用日志; 默认值: console.log 53 | logging: false, 54 |   55 | // 数据库的 sql 方言 56 | // - 当前支持: 'mysql', 'sqlite', 'postgres', 'mssql' 57 | dialect: 'mysql', 58 |   59 | // 你还可以将任何方言选项传递到底层方言库 60 | // - 默认是空 61 | // - 当前支持: 'mysql', 'postgres', 'mssql' 62 | dialectOptions: { 63 | socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock', 64 | supportBigNumbers: true, 65 | bigNumberStrings: true 66 | }, 67 |   68 | // sqlite 的存储引擎 69 | // - 默认值 ':memory:' 70 | storage: 'path/to/database.sqlite', 71 |   72 | // 禁止将未定义的值插入为NULL 73 | // - 默认值: false 74 | omitNull: true, 75 |   76 | // 是否使用本地库的标志 77 | // 如果是 'pg' -- 设置为 true 将允许 SSL 支持 78 | // - 默认值: false 79 | native: true, 80 |   81 | // 指定在调用 sequelize.define 时使用的选项 82 | // 如下示例: 83 | // define: {timestamps: false} 84 | // 这基本等同于: 85 | // sequelize.define(name, attributes, { timestamps: false }) 86 | // 没有必要像这样去设置每个定义的时间戳选项 87 | // 下面你看到的这些可能设置的键. 本章中都进行了说明 88 | define: { 89 | underscored: false 90 | freezeTableName: false, 91 | syncOnAssociation: true, 92 | charset: 'utf8', 93 | dialectOptions: { 94 | collate: 'utf8_general_ci' 95 | }, 96 | timestamps: true 97 | }, 98 |   99 | // 类似于同步:你可以定义始终强制同步模型 100 | sync: { force: true }, 101 |   102 | // 每次关联后进行同步(见下文)。 如果设置为 false,则需要在设置所有关联后手动进行同步。 默认值: true 103 | syncOnAssociation: true, 104 |   105 | // 使用连接池来减少数据库连接超载并提高速度 106 | // 当前仅支持 mysql 和 postgresql (从 v1.5.0 开始) 107 | pool: { max: 5, idle: 30}, 108 |   109 | // 用于确定如何根据 [lingo project](https://github.com/visionmedia/lingo) 将单词翻译成单数形式或复数形式 110 | // 选项为: en [默认], es 111 | language: 'en', 112 | 113 | // 每个事务的隔离级别. 默认是 REPEATABLE_READ 114 | // 可用选项: 115 | // READ_UNCOMMITTED 116 | // READ_COMMITTED 117 | // REPEATABLE_READ 118 | // SERIALIZABLE 119 | isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ 120 | }) 121 | ``` 122 | 123 | **提示:** 你可以通过传递一个方法为日志部分设置一个自定义方法。第一个参数是将被记录的字符串 。 124 | 125 | ## 读取复制 126 | 127 | Sequelize 支持读取复制,即在要执行 SELECT 查询时可以连接到多个服务器。 当你读取复制时,你指定一个或多个服务器作为读取副本,一个服务器充当写入主机,它处理所有写入和更新,并将其传播到副本(请注意,实际的复制进程为 *不是* 由 Sequelize 处理,而应该在 MySql 中设置)。 128 | 129 | ```js 130 | const sequelize = new Sequelize('database', null, null, { 131 | dialect: 'mysql', 132 | port: 3306 133 | replication: { 134 | read: [ 135 | { host: '8.8.8.8', username: 'anotherusernamethanroot', password: 'lolcats!' }, 136 | { host: 'localhost', username: 'root', password: null } 137 | ], 138 | write: { host: 'localhost', username: 'root', password: null } 139 | }, 140 | pool: { // 如果要覆盖用于读取池的选项,可以在此处进行 141 | max: 20, 142 | idle: 30000 143 | }, 144 | }) 145 | ``` 146 | 147 | 如果你有适用于所有副本的常规设置,则不需要为每个实例单独提供它们。在上面的代码中,数据库名称和端口被传播到所有副本。对于用户和密码也是如此, 如果你把它们用于任何一个副本。每个副本都有以下选项:`host`,`port`,`username`,`password`,`database`。 148 | 149 | Sequelize 使用池来管理与副本的连接。 默认选项为: 150 | 151 | ```js 152 | { 153 | max: 5, 154 | min: 0, 155 | idle: 10000, 156 | acquire: 10000, 157 | evict: 60000, 158 | handleDisconnects: true 159 | } 160 | ``` 161 | 162 | 如果要修改这些,可以在实例化 Sequelize 时作为选项传递池,如上所示。 163 | 164 | **注意:** 读复制当前只适用于MySQL! 165 | 166 | ## 方言 167 | 168 | 随着 Sequelize `1.6.0` 的发布,库可以独立于特定的方言。这意味着您必须自己添加相应的连接器库到您的项目。版本 1.7.0 stable 已经与连接器库(sequelize-mysql,sequelize-postgres等)捆绑在一起发布,但是这些软件包没有被维护,并且不会向2.0.0发布。 169 | 170 | ### MySQL 171 | 172 | 为了使 Sequelize 与 MySQL 完美结合,您需要安装 `mysql2@^1.0.0-rc.10` 或更高版本。 一旦完成,你可以这样使用它: 173 | 174 | ```js 175 | const sequelize = new Sequelize('database', 'username', 'password', { 176 | dialect: 'mysql' 177 | }) 178 | ``` 179 | 180 | **注意:** 您可以通过设置 `dialectOptions` 参数将选项直接传递给方言库. 查看 [Options][0] 181 | 获取例子 (目前只支持mysql). 182 | 183 | ### SQLite 184 | 185 | 对于 SQLite 兼容性,您将需要 `sqlite3 @〜3.0.0`。 配置 Sequelize 如下所示: 186 | 187 | ```js 188 | const sequelize = new Sequelize('database', 'username', 'password', { 189 | // 设置成 sqlite 190 | dialect: 'sqlite', 191 |   192 | // sqlite 的存储引擎 193 | // - default ':memory:' 194 | storage: 'path/to/database.sqlite' 195 | }) 196 | ``` 197 | 198 | 或者您也可以使用连接字符串以及路径: 199 | 200 | ```js 201 | const sequelize = new Sequelize('sqlite:/home/abs/path/dbname.db') 202 | const sequelize = new Sequelize('sqlite:relativePath/dbname.db') 203 | ``` 204 | 205 | ### PostgreSQL 206 | 207 | PostgreSQL 的库是 `pg@^5.0.0 || ^6.0.0 || ^7.0.0` 你只需要定义方言: 208 | 209 | ```js 210 | const sequelize = new Sequelize('database', 'username', 'password', { 211 | // 定义为 postgres 212 | dialect: 'postgres' 213 | }) 214 | ``` 215 | 216 | ### MSSQL 217 | 218 | MSSQL 的库是 `tedious@^1.7.0` 你只需要定义方言: 219 | 220 | ```js 221 | const sequelize = new Sequelize('database', 'username', 'password', { 222 | dialect: 'mssql' 223 | }) 224 | ``` 225 | 226 | ## 执行原始 SQL 查询 227 | 228 | 由于常常使用简单的方式来执行原始/已经准备好的SQL查询,所以可以使用“sequelize.query”函数。 229 | 230 | 这是它如何工作的: 231 | 232 | ```js 233 | // 原始查询的参数 234 | sequelize.query('your query', [, options]) 235 | 236 | // 简单的例子 237 | sequelize.query("SELECT * FROM myTable").then(myTableRows => { 238 | console.log(myTableRows) 239 | }) 240 | 241 | // 如果要返回 sequelize 实例,请使用模型选项。 242 | // 这样,你可以轻松地将查询映射到预定义的sequelize模型,例如: 243 | sequelize 244 | .query('SELECT * FROM projects', { model: Projects }) 245 | .then(projects => { 246 | // 每个记录现在将映射到项目的模型。 247 | console.log(projects) 248 | }) 249 | 250 | 251 | // 选项是具有以下键的对象: 252 | sequelize 253 | .query('SELECT 1', { 254 | // 用于记录查询的函数(或false) 255 | // 每个发送到服务器的SQL查询都会调用 256 | logging: console.log, 257 | 258 | // 如果 plain 是 TRUE ,则 sequelize 将只返回结果集的第一条记录。 259 | // 如果是 FALSE, 则是全部记录。 260 | plain: false, 261 | 262 | // 如果你没有查询的模型定义,请将其设置为true。 263 | raw: false 264 | }) 265 | 266 | // 注意第二个参数为null! 267 | // 即使我们在这里声明一个被调用,raw: true 将取代并返回一个原始对象。 268 | sequelize 269 | .query('SELECT * FROM projects', { raw: true }) 270 | .then(projects => { 271 | console.log(projects) 272 | }) 273 | ``` 274 | 275 | 查询中的替换可以通过两种不同的方式完成: 276 | 使用命名参数(以`:`开头),或者由未命名的 277 | 278 | 使用的语法取决于传递给函数的替换选项: 279 | 280 | * 如果一个数组被传递,`?` 将按照它们在数组中出现的顺序被替换 281 | * 如果传递一个对象,`:key`将被该对象的键替换。如果包含在查询中的对象未找到对应的键,则会抛出异常,反之亦然。 282 | 283 | ```js 284 | sequelize 285 | .query( 286 | 'SELECT * FROM projects WHERE status = ?', 287 | { raw: true, replacements: ['active'] 288 | ) 289 | .then(projects => { 290 | console.log(projects) 291 | }) 292 | 293 | sequelize 294 | .query( 295 | 'SELECT * FROM projects WHERE status = :status ', 296 | { raw: true, replacements: { status: 'active' } } 297 | ) 298 | .then(projects => { 299 | console.log(projects) 300 | }) 301 | ``` 302 | 303 | **注意一点:** 如果表的属性名称包含 " . ",则生成的对象将被嵌套: 304 | 305 | ```js 306 | sequelize.query('select 1 as `foo.bar.baz`').then(rows => { 307 | console.log(JSON.stringify(rows)) 308 | 309 | /* 310 | [{ 311 | "foo": { 312 | "bar": { 313 | "baz": 1 314 | } 315 | } 316 | }] 317 | */ 318 | }) 319 | ``` 320 | 321 | 322 | 323 | [0]: /docs/latest/usage#options 324 | -------------------------------------------------------------------------------- /instances.md: -------------------------------------------------------------------------------- 1 | # Instances - 实例 2 | 3 | ## 构建非持久性实例 4 | 5 | 为了创建定义类的实例,请执行以下操作。 如果你以前编写过 Ruby,你可能认识该语法。 使用 `build` - 该方法将返回一个未保存的对象,你要明确地保存它。 6 | 7 | ```js 8 | const project = Project.build({ 9 | title: 'my awesome project', 10 | description: 'woot woot. this will make me a rich man' 11 | }) 12 |   13 | const task = Task.build({ 14 | title: 'specify the project idea', 15 | description: 'bla', 16 | deadline: new Date() 17 | }) 18 | ``` 19 | 20 | 内置实例在定义时会自动获取默认值: 21 | 22 | ```js 23 | // 首先定义模型 24 | const Task = sequelize.define('task', { 25 | title: Sequelize.STRING, 26 | rating: { type: Sequelize.STRING, defaultValue: 3 } 27 | }) 28 |   29 | // 现在实例化一个对象 30 | const task = Task.build({title: 'very important task'}) 31 |   32 | task.title // ==> 'very important task' 33 | task.rating // ==> 3 34 | ``` 35 | 36 | 要将其存储在数据库中,请使用 `save` 方法并捕获事件(如果需要): 37 | 38 | ```js 39 | project.save().then(() => { 40 | // 回调 41 | }) 42 |   43 | task.save().catch(error => { 44 | // 呃 45 | }) 46 |   47 | // 还可以使用链式构建来保存和访问对象: 48 | Task 49 | .build({ title: 'foo', description: 'bar', deadline: new Date() }) 50 | .save() 51 | .then(anotherTask => { 52 | // 您现在可以使用变量 anotherTask 访问当前保存的任务 53 | }) 54 | .catch(error => { 55 | // Ooops,做一些错误处理 56 | }) 57 | ``` 58 | 59 | ## 创建持久性实例 60 | 61 | 除了构建对象之外,还需要一个明确的保存调用来存储在数据库中,可以通过一个命令执行所有这些步骤。 它被称为 `create`。 62 | 63 | ```js 64 | Task.create({ title: 'foo', description: 'bar', deadline: new Date() }).then(task => { 65 | // 你现在可以通过变量 task 来访问新创建的 task 66 | }) 67 | ``` 68 | 69 | 也可以通过 `create` 方法定义哪些属性可以设置。 如果你创建基于可由用户填写的表单的数据库条目,这将非常方便。 例如,使用这种方式,你可以限制 `User` 模型,仅设置 username 和 address,而不是 admin 标志: 70 | 71 | ```js 72 | User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] }).then(user => { 73 | // 我们假设 isAdmin 的默认值为 false: 74 | console.log(user.get({ 75 | plain: true 76 | })) // => { username: 'barfooz', isAdmin: false } 77 | }) 78 | ``` 79 | 80 | ## 更新 / 保存 / 持久化一个实例 81 | 82 | 现在可以更改一些值并将更改保存到数据库...有两种方法可以实现: 83 | 84 | ```js 85 | // 方法 1 86 | task.title = 'a very different title now' 87 | task.save().then(() => {}) 88 |   89 | // 方法 2 90 | task.update({ 91 | title: 'a very different title now' 92 | }).then(() => {}) 93 | ``` 94 | 95 | 通过传递列名数组,调用 `save` 时也可以定义哪些属性应该被保存。 当您基于先前定义的对象设置属性时,这是有用的。 例如。 如果您通过Web应用程序的形式获取对象的值。 此外,这在 `update` 内部使用。 它就像这样: 96 | 97 | ```js 98 | task.title = 'foooo' 99 | task.description = 'baaaaaar' 100 | task.save({fields: ['title']}).then(() => { 101 | // title 现在将是 “foooo”,而 description 与以前一样 102 | }) 103 |   104 | // 使用等效的 update 调用如下所示: 105 | task.update({ title: 'foooo', description: 'baaaaaar'}, {fields: ['title']}).then(() => { 106 | // title 现在将是 “foooo”,而 description 与以前一样 107 | }) 108 | ``` 109 | 110 | 当你调用 `save `而不改变任何属性的时候,这个方法什么都不执行。 111 | 112 | ## 销毁 / 删除持久性实例 113 | 114 | 创建对象并获得对象的引用后,可以从数据库中删除它。 相关的方法是 `destroy`: 115 | 116 | ```js 117 | Task.create({ title: 'a task' }).then(task => { 118 | // 获取到 task 对象... 119 | return task.destroy(); 120 | }).then(() => { 121 |  // task 对象已被销毁 122 | }) 123 | ``` 124 | 125 | 如果 `paranoid` 选项为 true,则不会删除该对象,而将 `deletedAt` 列设置为当前时间戳。 要强制删除,可以将 `force: true` 传递给 destroy 调用: 126 | 127 | ```js 128 | task.destroy({ force: true }) 129 | ``` 130 | 131 | ## 批量操作(一次创建,更新和销毁多行) 132 | 133 | 除了更新单个实例之外,你还可以一次创建,更新和删除多个实例。 调用你需要的方法 134 | 135 | * `Model.bulkCreate` 136 | * `Model.update` 137 | * `Model.destroy` 138 | 139 | 由于你使用多个模型,回调将不会返回DAO实例。 BulkCreate将返回一个模型实例/DAO的数组,但是它们不同于`create`,没有 autoIncrement 属性的结果值. `update` 和 `destroy` 将返回受影响的行数。 140 | 141 | 首先看下 bulkCreate 142 | 143 | ```js 144 | User.bulkCreate([ 145 | { username: 'barfooz', isAdmin: true }, 146 | { username: 'foo', isAdmin: true }, 147 | { username: 'bar', isAdmin: false } 148 | ]).then(() => { // 注意: 这里没有凭据, 然而现在你需要... 149 | return User.findAll(); 150 | }).then(users => { 151 | console.log(users) // ... 以获取 user 对象的数组 152 | }) 153 | ``` 154 | 155 | 一次更新几行: 156 | 157 | ```js 158 | Task.bulkCreate([ 159 | {subject: 'programming', status: 'executing'}, 160 | {subject: 'reading', status: 'executing'}, 161 | {subject: 'programming', status: 'finished'} 162 | ]).then(() => { 163 | return Task.update( 164 | { status: 'inactive' }, /* 设置属性的值 */, 165 | { where: { subject: 'programming' }} /* where 规则 */ 166 | ); 167 | }).spread((affectedCount, affectedRows) => { 168 | // .update 在数组中返回两个值,因此我们使用 .spread 169 | // 请注意,affectedRows 只支持以 returning: true 的方式进行定义 170 | 171 | // affectedCount 将会是 2 172 | return Task.findAll(); 173 | }).then(tasks => { 174 | console.log(tasks) // “programming” 任务都将处于 “inactive” 状态 175 | }) 176 | ``` 177 | 178 | 然后删除它们: 179 | 180 | ```js 181 | Task.bulkCreate([ 182 | {subject: 'programming', status: 'executing'}, 183 | {subject: 'reading', status: 'executing'}, 184 | {subject: 'programming', status: 'finished'} 185 | ]).then(() => { 186 | return Task.destroy({ 187 | where: { 188 | subject: 'programming' 189 | }, 190 | truncate: true /* 这将忽 where 并用 truncate table 替代 */ 191 | }); 192 | }).then(affectedRows => { 193 | // affectedRows 将会是 2 194 | return Task.findAll(); 195 | }).then(tasks => { 196 | console.log(tasks) // 显示 tasks 内容 197 | }) 198 | ``` 199 | 200 | 如果您直接从 user 接受值,则限制要实际插入的列可能会更好。`bulkCreate()` 接受一个选项对象作为第二个参数。 该对象可以有一个 `fields` 参数(一个数组),让它知道你想要明确构建哪些字段 201 | 202 | ```js 203 | User.bulkCreate([ 204 | { username: 'foo' }, 205 | { username: 'bar', admin: true} 206 | ], { fields: ['username'] }).then(() => { 207 | // admin 将不会被构建 208 | }) 209 | ``` 210 | 211 | `bulkCreate` 最初是成为 主流/快速 插入记录的方法,但是有时您希望能够同时插入多行而不牺牲模型验证,即使您明确地告诉 Sequelize 去筛选哪些列。 你可以通过在options对象中添加一个 `validate: true` 属性来实现。 212 | 213 | ```js 214 | const Tasks = sequelize.define('task', { 215 | name: { 216 | type: Sequelize.STRING, 217 | validate: { 218 | notNull: { args: true, msg: 'name cannot be null' } 219 | } 220 | }, 221 | code: { 222 | type: Sequelize.STRING, 223 | validate: { 224 | len: [3, 10] 225 | } 226 | } 227 | }) 228 |   229 | Tasks.bulkCreate([ 230 | {name: 'foo', code: '123'}, 231 | {code: '1234'}, 232 | {name: 'bar', code: '1'} 233 | ], { validate: true }).catch(errors => { 234 | 235 | /* console.log(errors) 看起来像这样: 236 | [ 237 | { record: 238 | ... 239 | errors: 240 | { name: 'SequelizeValidationError', 241 | message: 'Validation error', 242 | errors: [Object] } }, 243 | { record: 244 | ... 245 | errors: 246 | { name: 'SequelizeValidationError', 247 | message: 'Validation error', 248 | errors: [Object] } } 249 | ] 250 | */ 251 | 252 | }) 253 | ``` 254 | 255 | ## 一个实例的值 256 | 257 | 如果你记录一个实例,你会注意到有很多额外的东西。 为了隐藏这些东西并将其减少到非常有趣的信息,您可以使用 `get` 属性。 使用选项 `plain: true` 调用它将只返回一个实例的值。 258 | 259 | ```js 260 | Person.create({ 261 | name: 'Rambow', 262 | firstname: 'John' 263 | }).then(john => { 264 | console.log(john.get({ 265 | plain: true 266 | })) 267 | }) 268 |   269 | // 结果: 270 |   271 | // { name: 'Rambow', 272 | // firstname: 'John', 273 | // id: 1, 274 | // createdAt: Tue, 01 May 2012 19:12:16 GMT, 275 | // updatedAt: Tue, 01 May 2012 19:12:16 GMT 276 | // } 277 | ``` 278 | 279 | **提示:** 您还可以使用 `JSON.stringify(instance)` 将一个实例转换为 JSON。 基本上与 `values` 返回的相同。 280 | 281 | ## 重载实例 282 | 283 | 如果你需要让你的实例同步,你可以使用 `reload` 方法。 它将从数据库中获取当前数据,并覆盖调用该方法的模型的属性。 284 | 285 | ```js 286 | Person.findOne({ where: { name: 'john' } }).then(person => { 287 | person.name = 'jane' 288 | console.log(person.name) // 'jane' 289 |   290 | person.reload().then(() => { 291 | console.log(person.name) // 'john' 292 | }) 293 | }) 294 | ``` 295 | 296 | ## 递增 297 | 298 | 为了增加实例的值而不发生并发问题,您可以使用 `increment`。 299 | 300 | 首先,你可以定义一个字段和要添加的值。 301 | 302 | ```js 303 | User.findById(1).then(user => { 304 | return user.increment('my-integer-field', {by: 2}) 305 | }).then(/* ... */) 306 | ``` 307 | 308 | 然后,你可以定义多个字段和要添加到其中的值。 309 | 310 | ```js 311 | User.findById(1).then(user => { 312 | return user.increment([ 'my-integer-field', 'my-very-other-field' ], {by: 2}) 313 | }).then(/* ... */) 314 | ``` 315 | 316 | 最后,你可以定义一个包含字段及其递增值的对象。 317 | 318 | ```js 319 | User.findById(1).then(user => { 320 | return user.increment({ 321 | 'my-integer-field': 2, 322 | 'my-very-other-field': 3 323 | }) 324 | }).then(/* ... */) 325 | ``` 326 | 327 | ## 递减 328 | 329 | 为了减少一个实例的值而不遇到并发问题,你可以使用 `decrement`。 330 | 331 | 首先,你可以定义一个字段和要添加的值。 332 | 333 | ```js 334 | User.findById(1).then(user => { 335 | return user.decrement('my-integer-field', {by: 2}) 336 | }).then(/* ... */) 337 | ``` 338 | 339 | 然后,你可以定义多个字段和要添加到其中的值。 340 | 341 | ```js 342 | User.findById(1).then(user => { 343 | return user.decrement([ 'my-integer-field', 'my-very-other-field' ], {by: 2}) 344 | }).then(/* ... */) 345 | ``` 346 | 347 | 最后, 你可以定义一个包含字段及其递减值的对象。 348 | 349 | ```js 350 | User.findById(1).then(user => { 351 | return user.decrement({ 352 | 'my-integer-field': 2, 353 | 'my-very-other-field': 3 354 | }) 355 | }).then(/* ... */) 356 | ``` 357 | -------------------------------------------------------------------------------- /hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks - 钩子 2 | 3 | Hook(也称为生命周期事件)是执行 sequelize 调用之前和之后调用的函数。 例如,如果要在保存模型之前始终设置值,可以添加一个 `beforeUpdate` hook。 4 | 5 | 获取完整列表, 请查看 [Hooks file](https://github.com/sequelize/sequelize/blob/master/lib/hooks.js#L7). 6 | 7 | ## 操作清单 8 | 9 | ``` 10 | (1) 11 | beforeBulkCreate(instances, options) 12 | beforeBulkDestroy(options) 13 | beforeBulkUpdate(options) 14 | (2) 15 | beforeValidate(instance, options) 16 | (-) 17 | validate 18 | (3) 19 | afterValidate(instance, options) 20 | - or - 21 | validationFailed(instance, options, error) 22 | (4) 23 | beforeCreate(instance, options) 24 | beforeDestroy(instance, options) 25 | beforeUpdate(instance, options) 26 | beforeSave(instance, options) 27 | beforeUpsert(values, options) 28 | (-) 29 | create 30 | destroy 31 | update 32 | (5) 33 | afterCreate(instance, options) 34 | afterDestroy(instance, options) 35 | afterUpdate(instance, options) 36 | afterSave(instance, options) 37 | afterUpsert(created, options) 38 | (6) 39 | afterBulkCreate(instances, options) 40 | afterBulkDestroy(options) 41 | afterBulkUpdate(options) 42 | ``` 43 | 44 | ## 声明 Hook 45 | 46 | Hook 的参数通过引用传递。 这意味着您可以更改值,这将反映在insert / update语句中。 Hook 可能包含异步动作 - 在这种情况下,Hook 函数应该返回一个 promise。 47 | 48 | 目前有三种以编程方式添加 hook 的方法: 49 | 50 | ```js 51 | // 方法1 通过 .define() 方法 52 | const User = sequelize.define('user', { 53 | username: DataTypes.STRING, 54 | mood: { 55 | type: DataTypes.ENUM, 56 | values: ['happy', 'sad', 'neutral'] 57 | } 58 | }, { 59 | hooks: { 60 | beforeValidate: (user, options) => { 61 | user.mood = 'happy'; 62 | }, 63 | afterValidate: (user, options) => { 64 | user.username = 'Toni'; 65 | } 66 | } 67 | }); 68 | 69 | // 方法2 通过 . hook() 方法 (或其别名 .addHook() 方法) 70 | User.hook('beforeValidate', (user, options) => { 71 | user.mood = 'happy'; 72 | }); 73 | 74 | User.addHook('afterValidate', 'someCustomName', (user, options) => { 75 | return sequelize.Promise.reject(new Error("I'm afraid I can't let you do that!")); 76 | }); 77 | 78 | // 方法3 通过直接方法 79 | User.beforeCreate((user, options) => { 80 | return hashPassword(user.password).then(hashedPw => { 81 | user.password = hashedPw; 82 | }); 83 | }); 84 | 85 | User.afterValidate('myHookAfter', (user, options) => { 86 | user.username = 'Toni'; 87 | }); 88 | ``` 89 | 90 | ## 移除 Hook 91 | 92 | 只能删除有名称参数的 hook。 93 | 94 | ```js 95 | const Book = sequelize.define('book', { 96 | title: DataTypes.STRING 97 | }); 98 | 99 | Book.addHook('afterCreate', 'notifyUsers', (book, options) => { 100 | // ... 101 | }); 102 | 103 | Book.removeHook('afterCreate', 'notifyUsers'); 104 | ``` 105 | 106 | 你可以有很多同名的 hook。 调用 `.removeHook()` 将会删除它们。 107 | 108 | ## 全局 / 通用 Hook 109 | 110 | 全局 hook 是所有模型的 hook。 他们可以定义您想要的所有模型的行为,并且对插件特别有用。 它们可以用两种方式来定义,它们的语义略有不同: 111 | 112 | ### Sequelize.options.define (默认 hook) 113 | ```js 114 | const sequelize = new Sequelize(..., { 115 | define: { 116 | hooks: { 117 | beforeCreate: () => { 118 | // 做些什么 119 | } 120 | } 121 | } 122 | }); 123 | ``` 124 | 125 | 这将为所有模型添加一个默认 hook,如果模型没有定义自己的 `beforeCreate` hook,那么它将运行。 126 | 127 | ```js 128 | const User = sequelize.define('user'); 129 | const Project = sequelize.define('project', {}, { 130 | hooks: { 131 | beforeCreate: () => { 132 | // 做些其它什么 133 | } 134 | } 135 | }); 136 | 137 | User.create() // 运行全局 hook 138 | Project.create() // 运行其自身的 hook (因为全局 hook 被覆盖) 139 | ``` 140 | 141 | ### Sequelize.addHook (常驻 hook) 142 | 143 | ```js 144 | sequelize.addHook('beforeCreate', () => { 145 | // 做些什么 146 | }); 147 | ``` 148 | 149 | 这个 hook 总是在创建之前运行,无论模型是否指定了自己的 `beforeCreate` hook: 150 | 151 | 152 | ```js 153 | const User = sequelize.define('user'); 154 | const Project = sequelize.define('project', {}, { 155 | hooks: { 156 | beforeCreate: () => { 157 | // 做些其它什么 158 | } 159 | } 160 | }); 161 | 162 | User.create() // 运行全局 hook 163 | Project.create() //运行其自己的 hook 之后运行全局 hook 164 | ``` 165 | 166 | 本地 hook 总是在全局 hook 之前运行。 167 | 168 | 169 | ### 实例 Hook 170 | 171 | 当您编辑单个对象时,以下 hook 将触发 172 | 173 | ``` 174 | beforeValidate 175 | afterValidate or validationFailed 176 | beforeCreate / beforeUpdate / beforeDestroy 177 | afterCreate / afterUpdate / afterDestroy 178 | ``` 179 | 180 | ```js 181 | // ...定义 ... 182 | User.beforeCreate(user => { 183 | if (user.accessLevel > 10 && user.username !== "Boss") { 184 | throw new Error("您不能授予该用户10级以上的访问级别!") 185 | } 186 | }) 187 | ``` 188 | 189 | 此示例将返回错误: 190 | 191 | ```js 192 | User.create({username: 'Not a Boss', accessLevel: 20}).catch(err => { 193 | console.log(err); // 您不能授予该用户 10 级以上的访问级别! 194 | }); 195 | ``` 196 | 197 | 以下示例将返回成功: 198 | 199 | ```js 200 | User.create({username: 'Boss', accessLevel: 20}).then(user => { 201 | console.log(user); // 用户名为 Boss 和 accessLevel 为 20 的用户对象 202 | }); 203 | ``` 204 | 205 | ### 模型 Hook 206 | 207 | 有时,您将一次编辑多个记录,方法是使用模型上的 `bulkCreate, update, destroy` 方法。 当您使用以下方法之一时,将会触发以下内容: 208 | 209 | ``` 210 | beforeBulkCreate(instances, options) 211 | beforeBulkUpdate(options) 212 | beforeBulkDestroy(options) 213 | afterBulkCreate(instances, options) 214 | afterBulkUpdate(options) 215 | afterBulkDestroy(options) 216 | ``` 217 | 218 | 如果要为每个单独的记录触发 hook,连同批量 hook,您可以将 `personalHooks:true` 传递给调用。 219 | 220 | ```js 221 | Model.destroy({ where: {accessLevel: 0}, individualHooks: true}); 222 | // 将选择要删除的所有记录,并在每个实例删除之前 + 之后触发 223 | 224 | Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: true}); 225 | // 将选择要更新的所有记录,并在每个实例更新之前 + 之后触发 226 | ``` 227 | 228 | Hook 方法的 `options` 参数将是提供给相应方法或其克隆和扩展版本的第二个参数。 229 | 230 | ```js 231 | Model.beforeBulkCreate((records, {fields}) => { 232 | // records = 第一个参数发送到 .bulkCreate 233 | // fields = 第二个参数字段之一发送到 .bulkCreate 234 | }) 235 | 236 | Model.bulkCreate([ 237 | {username: 'Toni'}, // 部分记录参数 238 | {username: 'Tobi'} // 部分记录参数 239 | ], {fields: ['username']} // 选项参数 240 | ) 241 | 242 | Model.beforeBulkUpdate(({attributes, where}) => { 243 | // where - 第二个参数的克隆的字段之一发送到 .update 244 | // attributes - .update 的第二个参数的克隆的字段之一被用于扩展 245 | }) 246 | 247 | Model.update({gender: 'Male'} /*属性参数*/, { where: {username: 'Tom'}} /*where 参数*/) 248 | 249 | Model.beforeBulkDestroy(({where, individualHooks}) => { 250 | // individualHooks - 第二个参数被扩展的克隆被覆盖的默认值发送到 Model.destroy 251 | // where - 第二个参数的克隆的字段之一发送到 Model.destroy 252 | }) 253 | 254 | Model.destroy({ where: {username: 'Tom'}} /*where 参数*/) 255 | ``` 256 | 257 | 如果用 `updates.OnDuplicate` 参数使用 `Model.bulkCreate(...)` ,那么 hook 中对 `updatesOnDuplicate` 数组中没有给出的字段所做的更改将不会被持久保留到数据库。 但是,如果这是您想要的,则可以更改 hook 中的 updatesOnDuplicate 选项。 258 | 259 | ```js 260 | // 使用 updatesOnDuplicate 选项批量更新现有用户 261 | Users.bulkCreate([ 262 | { id: 1, isMember: true }, 263 | { id: 2, isMember: false } 264 | ], { 265 | updatesOnDuplicate: ['isMember'] 266 | }); 267 | 268 | User.beforeBulkCreate((users, options) => { 269 | for (const user of users) { 270 | if (user.isMember) { 271 | user.memberSince = new Date(); 272 | } 273 | } 274 | 275 | // 添加 memberSince 到 updatesOnDuplicate 否则 memberSince 期将不会被保存到数据库 276 | options.updatesOnDuplicate.push('memberSince'); 277 | }); 278 | ``` 279 | 280 | ## 关联 281 | 282 | 在大多数情况下,hook 对于相关联的实例而言将是一样的,除了几件事情之外。 283 | 284 | 1. 当使用 add/set 函数时,将运行 beforeUpdate/afterUpdate hook。 285 | 2. 调用 beforeDestroy/afterDestroy hook 的唯一方法是与 `onDelete:'cascade` 和参数 `hooks:true` 相关联。 例如: 286 | 287 | ```js 288 | const Projects = sequelize.define('projects', { 289 | title: DataTypes.STRING 290 | }); 291 | 292 | const Tasks = sequelize.define('tasks', { 293 | title: DataTypes.STRING 294 | }); 295 | 296 | Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true }); 297 | Tasks.belongsTo(Projects); 298 | ``` 299 | 300 | 该代码将在Tasks表上运行beforeDestroy / afterDestroy。 默认情况下,Sequelize会尝试尽可能优化您的查询。 在删除时调用级联,Sequelize将简单地执行一个 301 | 302 | ```sql 303 | DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey 304 | ``` 305 | 306 | 然而,添加 `hooks: true` 会明确告诉 Sequelize,优化不是你所关心的,并且会在关联的对象上执行一个 `SELECT`,并逐个删除每个实例,以便能够使用正确的参数调用 hook。 307 | 308 | 如果您的关联类型为 `n:m`,则在使用 `remove` 调用时,您可能有兴趣在直通模型上触发 hook。 在内部,sequelize 使用 `Model.destroy`,致使在每个实例上调用 `bulkDestroy` 而不是 `before / afterDestroy` hook。 309 | 310 | 这可以通过将 `{individualHooks:true}` 传递给 `remove` 调用来简单地解决,从而导致每个 hook 都通过实例对象被删除。 311 | 312 | 313 | ## 关于事务的注意事项 314 | 315 | 请注意,Sequelize 中的许多模型操作允许您在方法的 options 参数中指定事务。 如果在原始调用中 _指定_ 了一个事务,它将出现在传递给 hook 函数的 options 参数中。 例如,请参考以下代码段: 316 | 317 | ```js 318 | // 这里我们使用异步 hook 的 promise 风格,而不是回调。 319 | User.hook('afterCreate', (user, options) => { 320 | // 'transaction' 将在 options.transaction 中可用 321 | 322 | // 此操作将成为与原始 User.create 调用相同的事务的一部分。 323 | return User.update({ 324 | mood: 'sad' 325 | }, { 326 | where: { 327 | id: user.id 328 | }, 329 | transaction: options.transaction 330 | }); 331 | }); 332 | 333 | 334 | sequelize.transaction(transaction => { 335 | User.create({ 336 | username: 'someguy', 337 | mood: 'happy', 338 | transaction 339 | }); 340 | }); 341 | ``` 342 | 343 | 如果我们在上述代码中的 `User.update` 调用中未包含事务选项,则不会发生任何更改,因为在已提交挂起的事务之前,我们新创建的用户不存在于数据库中。 344 | 345 | ### 内部事务 346 | 347 | 要认识到 sequelize 可能会在某些操作(如 `Model.findOrCreate`)内部使用事务是非常重要的。 如果你的 hook 函数执行依赖对象在数据库中存在的读取或写入操作,或者修改对象的存储值,就像上一节中的例子一样,你应该总是指定 `{ transaction: options.transaction }`。 348 | 349 | 如果在处理操作的过程中已经调用了该 hook ,则这将确保您的依赖读/写是同一事务的一部分。 如果 hook 没有被处理,你只需要指定`{ transaction: null }` 并且可以预期默认行为。 -------------------------------------------------------------------------------- /querying.md: -------------------------------------------------------------------------------- 1 | # Querying - 查询 2 | 3 | ## 属性 4 | 5 | 想要只选择某些属性,可以使用 `attributes` 选项。 通常是传递一个数组: 6 | 7 | ```js 8 | Model.findAll({ 9 | attributes: ['foo', 'bar'] 10 | }); 11 | ``` 12 | ```sql 13 | SELECT foo, bar ... 14 | ``` 15 | 16 | 属性可以使用嵌套数组来重命名: 17 | 18 | ```js 19 | Model.findAll({ 20 | attributes: ['foo', ['bar', 'baz']] 21 | }); 22 | ``` 23 | ```sql 24 | SELECT foo, bar AS baz ... 25 | ``` 26 | 27 | 也可以使用 `sequelize.fn` 来进行聚合: 28 | 29 | ```js 30 | Model.findAll({ 31 | attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] 32 | }); 33 | ``` 34 | ```sql 35 | SELECT COUNT(hats) AS no_hats ... 36 | ``` 37 | 38 | 使用聚合功能时,必须给它一个别名,以便能够从模型中访问它。 在上面的例子中,您可以使用 `instance.get('no_hats')` 获得帽子数量。 39 | 40 | 有时,如果您只想添加聚合,则列出模型的所有属性可能令人厌烦: 41 | 42 | ```js 43 | // This is a tiresome way of getting the number of hats... 44 | Model.findAll({ 45 | attributes: ['id', 'foo', 'bar', 'baz', 'quz', [sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] 46 | }); 47 | 48 | // This is shorter, and less error prone because it still works if you add / remove attributes 49 | Model.findAll({ 50 | attributes: { include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] } 51 | }); 52 | ``` 53 | ```sql 54 | SELECT id, foo, bar, baz, quz, COUNT(hats) AS no_hats ... 55 | ``` 56 | 57 | 同样,它也可以删除一些指定的属性: 58 | 59 | ```js 60 | Model.findAll({ 61 | attributes: { exclude: ['baz'] } 62 | }); 63 | ``` 64 | ```sql 65 | SELECT id, foo, bar, quz ... 66 | ``` 67 | 68 | 69 | ## Where 70 | 71 | 无论您是通过 findAll/find 或批量 updates/destroys 进行查询,都可以传递一个 `where` 对象来过滤查询。 72 | 73 | `where` 通常用 attribute:value 键值对获取一个对象,其中 value 可以是匹配等式的数据或其他运算符的键值对象。 74 | 75 | 也可以通过嵌套 `or` 和 `and` `运算符` 的集合来生成复杂的 AND/OR 条件。 76 | 77 | ### 基础 78 | ```js 79 | const Op = Sequelize.Op; 80 | 81 | Post.findAll({ 82 | where: { 83 | authorId: 2 84 | } 85 | }); 86 | // SELECT * FROM post WHERE authorId = 2 87 | 88 | Post.findAll({ 89 | where: { 90 | authorId: 12, 91 | status: 'active' 92 | } 93 | }); 94 | // SELECT * FROM post WHERE authorId = 12 AND status = 'active'; 95 | 96 | Post.findAll({ 97 | where: { 98 | [Op.or]: [{authorId: 12}, {authorId: 13}] 99 | } 100 | }); 101 | // SELECT * FROM post WHERE authorId = 12 OR authorId = 13; 102 | 103 | Post.findAll({ 104 | where: { 105 | authorId: { 106 | [Op.or]: [12, 13] 107 | } 108 | } 109 | }); 110 | // SELECT * FROM post WHERE authorId = 12 OR authorId = 13; 111 | 112 | Post.destroy({ 113 | where: { 114 | status: 'inactive' 115 | } 116 | }); 117 | // DELETE FROM post WHERE status = 'inactive'; 118 | 119 | Post.update({ 120 | updatedAt: null, 121 | }, { 122 | where: { 123 | deletedAt: { 124 | [Op.ne]: null 125 | } 126 | } 127 | }); 128 | // UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL; 129 | 130 | Post.findAll({ 131 | where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6) 132 | }); 133 | // SELECT * FROM post WHERE char_length(status) = 6; 134 | ``` 135 | 136 | ### 操作符 137 | 138 | Sequelize 可用于创建更复杂比较的符号运算符 - 139 | 140 | ```js 141 | const Op = Sequelize.Op 142 | 143 | [Op.and]: {a: 5} // 且 (a = 5) 144 | [Op.or]: [{a: 5}, {a: 6}] // (a = 5 或 a = 6) 145 | [Op.gt]: 6, // id > 6 146 | [Op.gte]: 6, // id >= 6 147 | [Op.lt]: 10, // id < 10 148 | [Op.lte]: 10, // id <= 10 149 | [Op.ne]: 20, // id != 20 150 | [Op.eq]: 3, // = 3 151 | [Op.not]: true, // 不是 TRUE 152 | [Op.between]: [6, 10], // 在 6 和 10 之间 153 | [Op.notBetween]: [11, 15], // 不在 11 和 15 之间 154 | [Op.in]: [1, 2], // 在 [1, 2] 之中 155 | [Op.notIn]: [1, 2], // 不在 [1, 2] 之中 156 | [Op.like]: '%hat', // 包含 '%hat' 157 | [Op.notLike]: '%hat' // 不包含 '%hat' 158 | [Op.iLike]: '%hat' // 包含 '%hat' (不区分大小写) (仅限 PG) 159 | [Op.notILike]: '%hat' // 不包含 '%hat' (仅限 PG) 160 | [Op.regexp]: '^[h|a|t]' // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG) 161 | [Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG) 162 | [Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (仅限 PG) 163 | [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG) 164 | [Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike 165 | [Op.overlap]: [1, 2] // && [1, 2] (PG数组重叠运算符) 166 | [Op.contains]: [1, 2] // @> [1, 2] (PG数组包含运算符) 167 | [Op.contained]: [1, 2] // <@ [1, 2] (PG数组包含于运算符) 168 | [Op.any]: [2,3] // 任何数组[2, 3]::INTEGER (仅限PG) 169 | 170 | [Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 本例使用 PG 171 | ``` 172 | 173 | #### 范围选项 174 | 175 | 所有操作符都支持支持的范围类型查询。 176 | 177 | 请记住,提供的范围值也可以[定义绑定的 inclusion/exclusion](/manual/tutorial/models-definition.html#range-types)。 178 | 179 | ```js 180 | // 所有上述相等和不相等的操作符加上以下内容: 181 | 182 | [Op.contains]: 2 // @> '2'::integer (PG range contains element operator) 183 | [Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator) 184 | [Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator) 185 | [Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator) 186 | [Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator) 187 | [Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator) 188 | [Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator) 189 | [Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator) 190 | [Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator) 191 | ``` 192 | 193 | #### 组合 194 | ```js 195 | { 196 | rank: { 197 | [Op.or]: { 198 | [Op.lt]: 1000, 199 | [Op.eq]: null 200 | } 201 | } 202 | } 203 | // rank < 1000 OR rank IS NULL 204 | 205 | { 206 | createdAt: { 207 | [Op.lt]: new Date(), 208 | [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000) 209 | } 210 | } 211 | // createdAt < [timestamp] AND createdAt > [timestamp] 212 | 213 | { 214 | [Op.or]: [ 215 | { 216 | title: { 217 | [Op.like]: 'Boat%' 218 | } 219 | }, 220 | { 221 | description: { 222 | [Op.like]: '%boat%' 223 | } 224 | } 225 | ] 226 | } 227 | // title LIKE 'Boat%' OR description LIKE '%boat%' 228 | ``` 229 | 230 | #### 运算符别名 231 | 232 | Sequelize 允许将特定字符串设置为操作符的别名 - 233 | 234 | ```js 235 | const Op = Sequelize.Op; 236 | const operatorsAliases = { 237 | $gt: Op.gt 238 | } 239 | const connection = new Sequelize(db, user, pass, { operatorsAliases }) 240 | 241 | [Op.gt]: 6 // > 6 242 | $gt: 6 // 等同于使用 Op.gt (> 6) 243 | ``` 244 | 245 | 246 | #### 运算符安全性 247 | 248 | 使用没有任何别名的 Sequelize 可以提高安全性。 249 | 250 | 一些框架会自动将用户输入解析为js对象,如果您无法清理输入,则可能会将具有字符串运算符的对象注入到 Sequelize。 251 | 252 | 不带任何字符串别名将使运算符不太可能被注入,但您应该始终正确验证和清理用户输入。 253 | 254 | 由于向后兼容性原因Sequelize默认设置以下别名 - 255 | 256 | $eq, $ne, $gte, $gt, $lte, $lt, $not, $in, $notIn, $is, $like, $notLike, $iLike, $notILike, $regexp, $notRegexp, $iRegexp, $notIRegexp, $between, $notBetween, $overlap, $contains, $contained, $adjacent, $strictLeft, $strictRight, $noExtendRight, $noExtendLeft, $and, $or, $any, $all, $values, $col 257 | 258 | 目前,以下遗留别名也被设置,但计划在不久的将来完全删除 - 259 | 260 | ne, not, in, notIn, gte, gt, lte, lt, like, ilike, $ilike, nlike, $notlike, notilike, .., between, !.., notbetween, nbetween, overlap, &&, @>, <@ 261 | 262 | 为了更好的安全性,建议使用 `Sequelize.Op`,而不是依赖任何字符串别名。 您可以通过设置`operatorsAliases`选项来限制应用程序需要的别名,请记住要清理用户输入,特别是当您直接将它们传递给 Sequelize 方法时。 263 | 264 | ```js 265 | const Op = Sequelize.Op; 266 | 267 | // 不用任何操作符别名使用 sequelize 268 | const connection = new Sequelize(db, user, pass, { operatorsAliases: false }); 269 | 270 | // 只用 $and => Op.and 操作符别名使用 sequelize 271 | const connection2 = new Sequelize(db, user, pass, { operatorsAliases: { $and: Op.and } }); 272 | ``` 273 | 274 | 如果你使用默认别名并且不限制它们,Sequelize会发出警告。如果您想继续使用所有默认别名(不包括旧版别名)而不发出警告,您可以传递以下运算符参数 - 275 | 276 | ```js 277 | const Op = Sequelize.Op; 278 | const operatorsAliases = { 279 | $eq: Op.eq, 280 | $ne: Op.ne, 281 | $gte: Op.gte, 282 | $gt: Op.gt, 283 | $lte: Op.lte, 284 | $lt: Op.lt, 285 | $not: Op.not, 286 | $in: Op.in, 287 | $notIn: Op.notIn, 288 | $is: Op.is, 289 | $like: Op.like, 290 | $notLike: Op.notLike, 291 | $iLike: Op.iLike, 292 | $notILike: Op.notILike, 293 | $regexp: Op.regexp, 294 | $notRegexp: Op.notRegexp, 295 | $iRegexp: Op.iRegexp, 296 | $notIRegexp: Op.notIRegexp, 297 | $between: Op.between, 298 | $notBetween: Op.notBetween, 299 | $overlap: Op.overlap, 300 | $contains: Op.contains, 301 | $contained: Op.contained, 302 | $adjacent: Op.adjacent, 303 | $strictLeft: Op.strictLeft, 304 | $strictRight: Op.strictRight, 305 | $noExtendRight: Op.noExtendRight, 306 | $noExtendLeft: Op.noExtendLeft, 307 | $and: Op.and, 308 | $or: Op.or, 309 | $any: Op.any, 310 | $all: Op.all, 311 | $values: Op.values, 312 | $col: Op.col 313 | }; 314 | 315 | const connection = new Sequelize(db, user, pass, { operatorsAliases }); 316 | ``` 317 | 318 | 319 | ### JSONB 320 | 321 | JSONB 可以以三种不同的方式进行查询。 322 | 323 | #### 嵌套对象 324 | ```js 325 | { 326 | meta: { 327 | video: { 328 | url: { 329 | [Op.ne]: null 330 | } 331 | } 332 | } 333 | } 334 | ``` 335 | 336 | #### 嵌套键 337 | ```js 338 | { 339 | "meta.audio.length": { 340 | [Op.gt]: 20 341 | } 342 | } 343 | ``` 344 | 345 | #### 外包裹 346 | ```js 347 | { 348 | "meta": { 349 | [Op.contains]: { 350 | site: { 351 | url: 'http://google.com' 352 | } 353 | } 354 | } 355 | } 356 | ``` 357 | 358 | ### 关系 / 关联 359 | ```js 360 | // 找到所有具有至少一个 task 的 project,其中 task.state === project.state 361 | Project.findAll({ 362 | include: [{ 363 | model: Task, 364 | where: { state: Sequelize.col('project.state') } 365 | }] 366 | }) 367 | ``` 368 | 369 | ## 分页 / 限制 370 | 371 | ```js 372 | // 获取10个实例/行 373 | Project.findAll({ limit: 10 }) 374 | 375 | // 跳过8个实例/行 376 | Project.findAll({ offset: 8 }) 377 | 378 | // 跳过5个实例,然后取5个 379 | Project.findAll({ offset: 5, limit: 5 }) 380 | ``` 381 | 382 | ## 排序 383 | 384 | `order` 需要一个条目的数组来排序查询或者一个 sequelize 方法。一般来说,你将要使用任一属性的 tuple/array,并确定排序的正反方向。 385 | 386 | ```js 387 | Subtask.findAll({ 388 | order: [ 389 | // 将转义用户名,并根据有效的方向参数列表验证DESC 390 | ['title', 'DESC'], 391 | 392 | // 将按最大值排序(age) 393 | sequelize.fn('max', sequelize.col('age')), 394 | 395 | // 将按最大顺序(age) DESC 396 | [sequelize.fn('max', sequelize.col('age')), 'DESC'], 397 | 398 | // 将按 otherfunction 排序(`col1`, 12, 'lalala') DESC 399 | [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], 400 | 401 | // 将使用模型名称作为关联的名称排序关联模型的 created_at。 402 | [Task, 'createdAt', 'DESC'], 403 | 404 | // Will order through an associated model's created_at using the model names as the associations' names. 405 | [Task, Project, 'createdAt', 'DESC'], 406 | 407 | // 将使用关联的名称由关联模型的created_at排序。 408 | ['Task', 'createdAt', 'DESC'], 409 | 410 | // Will order by a nested associated model's created_at using the names of the associations. 411 | ['Task', 'Project', 'createdAt', 'DESC'], 412 | 413 | // Will order by an associated model's created_at using an association object. (优选方法) 414 | [Subtask.associations.Task, 'createdAt', 'DESC'], 415 | 416 | // Will order by a nested associated model's created_at using association objects. (优选方法) 417 | [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'], 418 | 419 | // Will order by an associated model's created_at using a simple association object. 420 | [{model: Task, as: 'Task'}, 'createdAt', 'DESC'], 421 | 422 | // 嵌套关联模型的 created_at 简单关联对象排序 423 | [{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC'] 424 | ] 425 | 426 | // 将按年龄最大值降序排列 427 | order: sequelize.literal('max(age) DESC') 428 | 429 | // 按最年龄大值升序排列,当省略排序条件时默认是升序排列 430 | order: sequelize.fn('max', sequelize.col('age')) 431 | 432 | // 按升序排列是省略排序条件的默认顺序 433 | order: sequelize.col('age') 434 | }) 435 | ``` 436 | -------------------------------------------------------------------------------- /migrations.md: -------------------------------------------------------------------------------- 1 | # Migrations - 迁移 2 | 3 | 就像您使用Git / SVN来管理源代码的更改一样,您可以使用迁移来跟踪数据库的更改。 通过迁移,您可以将现有的数据库转移到另一个状态,反之亦然:这些状态转换将保存在迁移文件中,它们描述了如何进入新状态以及如何还原更改以恢复旧状态。 4 | 5 | 您将需要[Sequelize CLI][0]。 CLI支持迁移和项目引导。 6 | 7 | ## 命令行界面 8 | 9 | ### 安装命令行界面 10 | 11 | 让我们从安装CLI开始,你可以在 [这里][0] 找到说明。 最推荐的方式是这样安装 12 | 13 | ```bash 14 | $ npm install --save sequelize-cli 15 | ``` 16 | 17 | ### 引导 18 | 19 | 要创建一个空项目,你需要执行 `init` 命令 20 | 21 | ```bash 22 | $ node_modules/.bin/sequelize init 23 | ``` 24 | 25 | 这将创建以下文件夹 26 | 27 | - `config`, 包含配置文件,它告诉CLI如何连接数据库 28 | - `models`,包含您的项目的所有模型 29 | - `migrations`, 包含所有迁移文件 30 | - `seeders`, 包含所有种子文件 31 | 32 | #### 结构 33 | 34 | 在继续进行之前,我们需要告诉 CLI 如何连接到数据库。 为此,可以打开默认配置文件 `config/config.json`。 看起来像这样 35 | 36 | ```json 37 | { 38 | development: { 39 | username: 'root', 40 | password: null, 41 | database: 'database_development', 42 | host: '127.0.0.1', 43 | dialect: 'mysql' 44 | }, 45 | test: { 46 | username: 'root', 47 | password: null, 48 | database: 'database_test', 49 | host: '127.0.0.1', 50 | dialect: 'mysql' 51 | }, 52 | production: { 53 | username: 'root', 54 | password: null, 55 | database: 'database_production', 56 | host: '127.0.0.1', 57 | dialect: 'mysql' 58 | } 59 | } 60 | ``` 61 | 62 | 现在编辑此文件并设置正确的数据库凭据和方言。 63 | 64 | **注意:** _如果你的数据库还不存在,你可以调用 `db:create` 命令。 通过正确的访问,它将为您创建该数据库。_ 65 | 66 | ### 创建第一个模型(和迁移) 67 | 68 | 一旦您正确配置了CLI配置文件,您就可以首先创建迁移。 它像执行一个简单的命令一样简单。 69 | 70 | 我们将使用 `model:generate` 命令。 此命令需要两个选项 71 | 72 | - `name`, 模型的名称 73 | - `attributes`, 模型的属性列表 74 | 75 | 让我们创建一个名叫 `User` 的模型 76 | 77 | ```bash 78 | $ node_modules/.bin/sequelize model:generate --name User --attributes firstName:string,lastName:string,email:string 79 | ``` 80 | 81 | 这将发生以下事情 82 | 83 | - 在 `models` 文件夹中创建了一个 `user` 模型文件 84 | - 在 `migrations` 文件夹中创建了一个名字像 `XXXXXXXXXXXXXX-create-user.js` 的迁移文件 85 | 86 | **注意:** _Sequelize 将只使用模型文件,它是表描述。另一边,迁移文件是该模型的更改,或更具体的是说 CLI 所使用的表。 处理迁移,如提交或日志,以进行数据库的某些更改。 _ 87 | 88 | ### 运行迁移 89 | 90 | 直到这一步,CLI没有将任何东西插入数据库。 我们刚刚为我们的第一个模型 `User` 创建了必需的模型和迁移文件。 现在要在数据库中实际创建该表,需要运行 `db:migrate` 命令。 91 | 92 | ```bash 93 | $ node_modules/.bin/sequelize db:migrate 94 | ``` 95 | 96 | 此命令将执行这些步骤 97 | 98 | - 将在数据库中确保一个名为 `SequelizeMeta` 的表。 此表用于记录在当前数据库上运行的迁移 99 | - 开始寻找尚未运行的任何迁移文件。 这可以通过检查 `SequelizeMeta` 表。 在这个例子中,它将运行我们在最后一步中创建的 `XXXXXXXXXXXXXX-create-user.js` 迁移,。 100 | - 创建一个名为 `Users` 的表,其中包含其迁移文件中指定的所有列。 101 | 102 | ### 撤消迁移 103 | 104 | 现在我们的表已创建并保存在数据库中。 通过迁移,只需运行命令即可恢复为旧状态。 105 | 106 | 您可以使用 `db:migrate:undo`,这个命令将会恢复最近的迁移。 107 | 108 | ```bash 109 | $ node_modules/.bin/sequelize db:migrate:undo 110 | ``` 111 | 112 | 通过使用 `db:migrate:undo:all` 命令撤消所有迁移,可以恢复到初始状态。 您还可以通过将其名称传递到 `--to` 选项中来恢复到特定的迁移。 113 | 114 | ```bash 115 | $ node_modules/.bin/sequelize db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js 116 | ``` 117 | 118 | ### 创建第一个种子 119 | 120 | 假设我们希望在默认情况下将一些数据插入到几个表中。 如果我们跟进前面的例子,我们可以考虑为 `User` 表创建演示用户。 121 | 122 | 要管理所有数据迁移,您可以使用 `seeders`。 种子文件是数据的一些变化,可用于使用样本数据或测试数据填充数据库表。 123 | 124 | 让我们创建一个种子文件,它会将一个演示用户添加到我们的 `User` 表中。 125 | 126 | ```bash 127 | $ node_modules/.bin/sequelize seed:generate --name demo-user 128 | ``` 129 | 130 | 这个命令将会在 `seeders` 文件夹中创建一个种子文件。文件名看起来像是 `XXXXXXXXXXXXXX-demo-user.js`,它遵循相同的 `up/down` 语义,如迁移文件。 131 | 132 | 现在我们应该编辑这个文件,将演示用户插入`User`表。 133 | 134 | ```js 135 | 'use strict'; 136 | 137 | module.exports = { 138 | up: (queryInterface, Sequelize) => { 139 | return queryInterface.bulkInsert('Users', [{ 140 | firstName: 'John', 141 | lastName: 'Doe', 142 | email: 'demo@demo.com' 143 | }], {}); 144 | }, 145 | 146 | down: (queryInterface, Sequelize) => { 147 | return queryInterface.bulkDelete('Users', null, {}); 148 | } 149 | }; 150 | 151 | ``` 152 | 153 | ### 运行种子 154 | 155 | 在上一步中,你创建了一个种子文件。 但它还没有保存到数据库。 为此,我们需要运行一个简单的命令。 156 | 157 | ```bash 158 | $ node_modules/.bin/sequelize db:seed:all 159 | ``` 160 | 161 | 这将执行该种子文件,您将有一个演示用户插入 `User` 表。 162 | 163 | **注意:** _ `seeders` 执行不会存储在任何使用 `SequelizeMeta` 表的迁移的地方。 如果你想覆盖这个,请阅读 `存储` 部分_ 164 | 165 | ### 撤销种子 166 | 167 | Seeders 如果使用了任何存储那么就可以被撤消。 有两个可用的命令 168 | 169 | 如果你想撤消最近的种子 170 | 171 | ```bash 172 | node_modules/.bin/sequelize db:seed:undo 173 | ``` 174 | 175 | 如果你想撤消所有的种子 176 | 177 | ```bash 178 | node_modules/.bin/sequelize db:seed:undo:all 179 | ``` 180 | 181 | ## 高级专题 182 | 183 | ### 迁移框架 184 | 185 | 以下框架显示了一个典型的迁移文件。 186 | 187 | ```js 188 | module.exports = { 189 | up: (queryInterface, Sequelize) => { 190 | // 转变为新状态的逻辑 191 | }, 192 |   193 | down: (queryInterface, Sequelize) => { 194 | // 恢复更改的逻辑 195 | } 196 | } 197 | ``` 198 | 199 | 传递的 `queryInterface` 对象可以用来修改数据库。 `Sequelize` 对象存储可用的数据类型,如 `STRING` 或 `INTEGER`。 函数 `up` 或 `down` 应该返回一个 `Promise` 。 让我们来看一个例子 200 | 201 | 202 | ```js 203 | module.exports = { 204 | up: (queryInterface, Sequelize) => { 205 | return queryInterface.createTable('Person', { 206 | name: Sequelize.STRING, 207 | isBetaMember: { 208 | type: Sequelize.BOOLEAN, 209 | defaultValue: false, 210 | allowNull: false 211 | } 212 | }); 213 | }, 214 | down: (queryInterface, Sequelize) => { 215 | return queryInterface.dropTable('Person'); 216 | } 217 | } 218 | ``` 219 | 220 | ### `.sequelizerc` 文件 221 | 222 | 这是一个特殊的配置文件。 它允许您指定通常作为参数传递给CLI的各种选项。 在某些情况下,您可以使用它。 223 | 224 | - 你想要覆盖到 `migrations`, `models`, `seeders` 或 `config` 文件夹的路径. 225 | - 你想要重命名 `config.json` 成为别的名字比如 `database.json` 226 | 227 | 还有更多的, 让我们看一下如何使用这个文件进行自定义配置。 228 | 229 | 对于初学者,可以在项目的根目录中创建一个空文件。 230 | 231 | ```bash 232 | $ touch .sequelizerc 233 | ``` 234 | 235 | 现在可以使用示例配置。 236 | 237 | ```js 238 | const path = require('path'); 239 | 240 | module.exports = { 241 | 'config': path.resolve('config', 'database.json'), 242 | 'models-path': path.resolve('db', 'models'), 243 | 'seeders-path': path.resolve('db', 'seeders'), 244 | 'migrations-path': path.resolve('db', 'migrations') 245 | } 246 | ``` 247 | 248 | 通过这个配置你告诉CLI: 249 | 250 | - 使用 `config/database.json` 文件来配置设置 251 | - 使用 `db/models` 作为模型文件夹 252 | - 使用 `db/seeders` 作为种子文件夹 253 | - 使用 `db/migrations` 作为迁移文件夹 254 | 255 | ### 动态配置 256 | 257 | 配置文件是默认的一个名为 `config.json` 的JSON文件。 但有时你想执行一些代码或访问环境变量,这在JSON文件中是不可能的。 258 | 259 | Sequelize CLI可以从“JSON”和“JS”文件中读取。 这可以用`.sequelizerc`文件设置。 让我们来看一下 260 | 261 | 首先,您需要在项目的根文件夹中创建一个 `.sequelizerc` 文件。 该文件应该覆盖 `JS` 文件的配置路径。 推荐这个 262 | 263 | ```js 264 | const path = require('path'); 265 | 266 | module.exports = { 267 | 'config': path.resolve('config', 'config.js') 268 | } 269 | ``` 270 | 271 | 现在,Sequelize CLI将加载 `config/config.js` 以获取配置选项。 由于这是一个JS文件,您可以执行任何代码并导出最终的动态配置文件。 272 | 273 | 一个 `config/config.js` 文件的例子 274 | 275 | ```js 276 | const fs = require('fs'); 277 | 278 | module.exports = { 279 | development: { 280 | username: 'database_dev', 281 | password: 'database_dev', 282 | database: 'database_dev', 283 | host: '127.0.0.1', 284 | dialect: 'mysql' 285 | }, 286 | test: { 287 | username: 'database_test', 288 | password: null, 289 | database: 'database_test', 290 | host: '127.0.0.1', 291 | dialect: 'mysql' 292 | }, 293 | production: { 294 | username: process.env.DB_USERNAME, 295 | password: process.env.DB_PASSWORD, 296 | database: process.env.DB_NAME, 297 | host: process.env.DB_HOSTNAME, 298 | dialect: 'mysql', 299 | dialectOptions: { 300 | ssl: { 301 | ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') 302 | } 303 | } 304 | } 305 | }; 306 | ``` 307 | 308 | ### 使用环境变量 309 | 310 | 使用CLI,您可以直接访问 `config/config.js` 内的环境变量。 您可以使用 `.sequelizerc` 来告诉CLI使用 `config/config.js` 进行配置。 这在上一节中有所解释。 311 | 312 | 然后你可以使用正确的环境变量来暴露文件。 313 | 314 | ```js 315 | module.exports = { 316 | development: { 317 | username: 'database_dev', 318 | password: 'database_dev', 319 | database: 'database_dev', 320 | host: '127.0.0.1', 321 | dialect: 'mysql' 322 | }, 323 | test: { 324 | username: process.env.CI_DB_USERNAME, 325 | password: process.env.CI_DB_PASSWORD, 326 | database: process.env.CI_DB_NAME, 327 | host: '127.0.0.1', 328 | dialect: 'mysql' 329 | }, 330 | production: { 331 | username: process.env.PROD_DB_USERNAME, 332 | password: process.env.PROD_DB_PASSWORD, 333 | database: process.env.PROD_DB_NAME, 334 | host: process.env.PROD_DB_HOSTNAME, 335 | dialect: 'mysql' 336 | } 337 | ``` 338 | 339 | ### 指定方言选项 340 | 341 | 有时你想指定一个 dialectOption,如果它是一个通用配置,你可以将其添加到 `config/config.json` 中。 有时你想执行一些代码来获取 dialectOptions,你应该为这些情况使用动态配置文件。 342 | 343 | ```json 344 | { 345 | "production": { 346 | "dialect":"mysql", 347 | "dialectOptions": { 348 | "bigNumberStrings": true 349 | } 350 | } 351 | } 352 | ``` 353 | 354 | ### 生产用途 355 | 356 | 有关在生产环境中使用CLI和迁移设置的一些提示。 357 | 358 | 1) 使用环境变量进行配置设置。 这是通过动态配置更好地实现的。 样品生产安全配置可能看起来像 359 | 360 | ```js 361 | const fs = require('fs'); 362 | 363 | module.exports = { 364 | development: { 365 | username: 'database_dev', 366 | password: 'database_dev', 367 | database: 'database_dev', 368 | host: '127.0.0.1', 369 | dialect: 'mysql' 370 | }, 371 | test: { 372 | username: 'database_test', 373 | password: null, 374 | database: 'database_test', 375 | host: '127.0.0.1', 376 | dialect: 'mysql' 377 | }, 378 | production: { 379 | username: process.env.DB_USERNAME, 380 | password: process.env.DB_PASSWORD, 381 | database: process.env.DB_NAME, 382 | host: process.env.DB_HOSTNAME, 383 | dialect: 'mysql', 384 | dialectOptions: { 385 | ssl: { 386 | ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') 387 | } 388 | } 389 | } 390 | }; 391 | ``` 392 | 393 | 我们的目标是为各种数据库秘密使用环境变量,而不是意外检查它们来源控制。 394 | 395 | ### 存储 396 | 397 | 可以使用三种类型的存储:`sequelize`,`json`和`none`。 398 | 399 | - `sequelize` : 将迁移和种子存储在 sequelize 数据库的表中 400 | - `json` : 将迁移和种子存储在json文件上 401 | - `none` : 不存储任何迁移/种子 402 | 403 | 404 | #### 迁移存储 405 | 406 | 默认情况下,CLI 将在您的数据库中创建一个名为 `SequelizeMeta` 的表,其中包含每个执行迁移的条目。 要更改此行为,可以在配置文件中添加三个选项。 使用 `migrationStorage` 可以选择要用于迁移的存储类型。 如果选择 `json`,可以使用 `migrationStoragePath` 指定文件的路径,或者 CLI 将写入 `sequelize-meta.json` 文件。 如果要将数据保存在数据库中,请使用 `sequelize`,但是要使用其他表格,可以使用 `migrationStorageTableName`. 407 | 408 | ```json 409 | { 410 | "development": { 411 | "username": "root", 412 | "password": null, 413 | "database": "database_development", 414 | "host": "127.0.0.1", 415 | "dialect": "mysql", 416 | 417 | // 使用不同的存储类型. Default: sequelize 418 | "migrationStorage": "json", 419 | 420 | // 使用不同的文件名. Default: sequelize-meta.json 421 | "migrationStoragePath": "sequelizeMeta.json", 422 | 423 | // 使用不同的表名. Default: SequelizeMeta 424 | "migrationStorageTableName": "sequelize_meta" 425 | } 426 | } 427 | ``` 428 | 429 | **注意:** _不推荐使用 `none` 存储作为迁移存储。 如果您决定使用它,请注意将会没有任何移动记录或没有运行的记录._ 430 | 431 | #### 种子储存 432 | 433 | 默认情况下,CLI 不会保存任何被执行的种子。 如果您选择更改此行为(!),则可以在配置文件中使用 `seederStorage` 来更改存储类型。 如果选择 `json`,可以使用 `seederStoragePath` 指定文件的路径,或者 CLI 将写入文件 `sequelize-data.json`。 如果要将数据保存在数据库中,请使用 `sequelize`,您可以使用 `seederStorageTableName` 指定表名,否则将默认为`SequelizeData`。 434 | 435 | ```json 436 | { 437 | "development": { 438 | "username": "root", 439 | "password": null, 440 | "database": "database_development", 441 | "host": "127.0.0.1", 442 | "dialect": "mysql", 443 | // 使用不同的存储空间. Default: none 444 | "seederStorage": "json", 445 | // 使用不同的文件名. Default: sequelize-data.json 446 | "seederStoragePath": "sequelizeData.json", 447 | // 使用不同的表名 Default: SequelizeData 448 | "seederStorageTableName": "sequelize_data" 449 | } 450 | } 451 | ``` 452 | 453 | ### 配置连接字符串 454 | 455 | 作为 `--config` 选项的替代方法,可以使用定义数据库的配置文件,您可以使用 `--url` 选项传递连接字符串。 例如: 456 | 457 | ```bash 458 | $ node_modules/.bin/sequelize db:migrate --url 'mysql://root:password@mysql_host.com/database_name' 459 | ``` 460 | 461 | ### 通过SSL连接 462 | 463 | 确保ssl在 `dialectOptions` 和基本配置中指定。 464 | 465 | ```json 466 | { 467 | "production": { 468 | "dialect":"postgres", 469 | "ssl": true, 470 | "dialectOptions": { 471 | "ssl": true 472 | } 473 | } 474 | } 475 | ``` 476 | 477 | ### 程序化使用 478 | 479 | Sequelize 有一个 [姊妹库][1],用于以编程方式处理迁移任务的执行和记录。 480 | 481 | ## 查询界面 482 | 483 | 使用 `queryInterface` 对象描述之前,您可以更改数据库模式。 查看完整的公共方法列表,它支持 [QueryInterface API][2] 484 | 485 | 486 | [0]: https://github.com/sequelize/cli 487 | [1]: https://github.com/sequelize/umzug 488 | [2]: /class/lib/query-interface.js~QueryInterface.html 489 | -------------------------------------------------------------------------------- /models-usage.md: -------------------------------------------------------------------------------- 1 | # Model usage - 模型使用 2 | 3 | ## 数据检索/查找器 4 | 5 | Finder 方法旨在从数据库查询数据。 他们 **不** 返回简单的对象,而是返回模型实例。 因为 finder 方法返回模型实例,您可以按照 [*实例*](/manual/tutorial/instances.html) 的文档中所述,为结果调用任何模型实例成员。 6 | 7 | 在本文中,我们将探讨 finder 方法可以做什么: 8 | 9 | ### `find` - 搜索数据库中的一个特定元素 10 | ```js 11 | // 搜索已知的ids 12 | Project.findById(123).then(project => { 13 | // project 将是 Project的一个实例,并具有在表中存为 id 123 条目的内容。 14 | // 如果没有定义这样的条目,你将获得null 15 | }) 16 | 17 | // 搜索属性 18 | Project.findOne({ where: {title: 'aProject'} }).then(project => { 19 | // project 将是 Projects 表中 title 为 'aProject' 的第一个条目 || null 20 | }) 21 | 22 | 23 | Project.findOne({ 24 | where: {title: 'aProject'}, 25 | attributes: ['id', ['name', 'title']] 26 | }).then(project => { 27 | // project 将是 Projects 表中 title 为 'aProject' 的第一个条目 || null 28 | // project.title 将包含 project 的 name 29 | }) 30 | ``` 31 | 32 | ### `findOrCreate` - 搜索特定元素或创建它(如果不可用) 33 | 34 | 方法 `findOrCreate` 可用于检查数据库中是否已存在某个元素。 如果是这种情况,则该方法将生成相应的实例。 如果元素不存在,将会被创建。 35 | 36 | 如果是这种情况,则该方法将导致相应的实例。 如果元素不存在,将会被创建。 37 | 38 | 假设我们有一个空的数据库,一个 `User` 模型有一个 `username` 和 `job`。 39 | 40 | ```js 41 | User 42 | .findOrCreate({where: {username: 'sdepold'}, defaults: {job: 'Technical Lead JavaScript'}}) 43 | .spread((user, created) => { 44 | console.log(user.get({ 45 | plain: true 46 | })) 47 | console.log(created) 48 | 49 | /* 50 | findOrCreate 返回一个包含已找到或创建的对象的数组,找到或创建的对象和一个布尔值,如果创建一个新对象将为true,否则为false,像这样: 51 | 52 | [ { 53 | username: 'sdepold', 54 | job: 'Technical Lead JavaScript', 55 | id: 1, 56 | createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), 57 | updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) 58 | }, 59 | true ] 60 | 61 | 在上面的例子中,".spread" 将数组分成2部分,并将它们作为参数传递给回调函数,在这种情况下将它们视为 "user" 和 "created" 。(所以“user”将是返回数组的索引0的对象,并且 "created" 将等于 "true"。) 62 | */ 63 | }) 64 | ``` 65 | 66 | 代码创建了一个新的实例。 所以当我们已经有一个实例了 ... 67 | 68 | ```js 69 | User.create({ username: 'fnord', job: 'omnomnom' }) 70 | .then(() => User.findOrCreate({where: {username: 'fnord'}, defaults: {job: 'something else'}})) 71 | .spread((user, created) => { 72 | console.log(user.get({ 73 | plain: true 74 | })) 75 | console.log(created) 76 | 77 | /* 78 | 在这个例子中,findOrCreate 返回一个如下的数组: 79 | [ { 80 | username: 'fnord', 81 | job: 'omnomnom', 82 | id: 2, 83 | createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), 84 | updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) 85 | }, 86 | false 87 | ] 88 | 由findOrCreate返回的数组通过 ".spread" 扩展为两部分,并且这些部分将作为2个参数传递给回调函数,在这种情况下将其视为 "user" 和 "created" 。(所以“user”将是返回数组的索引0的对象,并且 "created" 将等于 "false"。) 89 | */ 90 | }) 91 | ``` 92 | 93 | ...现有条目将不会更改。 看到第二个用户的 "job",并且实际上创建操作是假的。 94 | 95 | ### `findAndCountAll` - 在数据库中搜索多个元素,返回数据和总计数 96 | 97 | 这是一个方便的方法,它结合了 `findAll` 和 `count`(见下文),当处理与分页相关的查询时,这是有用的,你想用 `limit` 和 `offset` 检索数据,但也需要知道总数与查询匹配的记录数: 98 | 99 | 处理程序成功将始终接收具有两个属性的对象: 100 | 101 | * `count` - 一个整数,总数记录匹配where语句 102 | * `rows` - 一个数组对象,记录在limit和offset范围内匹配where语句, 103 | 104 | ```js 105 | Project 106 | .findAndCountAll({ 107 | where: { 108 | title: { 109 | [Op.like]: 'foo%' 110 | } 111 | }, 112 | offset: 10, 113 | limit: 2 114 | }) 115 | .then(result => { 116 | console.log(result.count); 117 | console.log(result.rows); 118 | }); 119 | ``` 120 | 121 | `findAndCountAll` 也支持 include。 只有标记为 `required` 的 include 将被添加到计数部分: 122 | 123 | 假设您想查找附有个人资料的所有用户: 124 | 125 | ```js 126 | User.findAndCountAll({ 127 | include: [ 128 | { model: Profile, required: true} 129 | ], 130 | limit: 3 131 | }); 132 | ``` 133 | 134 | 因为 `Profile` 的 include 有 `required` 设置,这将导致内部连接,并且只有具有 profile 的用户将被计数。 如果我们从 include 中删除`required`,那么有和没有 profile 的用户都将被计数。 在include中添加一个 `where` 语句会自动使它成为 required: 135 | 136 | ```js 137 | User.findAndCountAll({ 138 | include: [ 139 | { model: Profile, where: { active: true }} 140 | ], 141 | limit: 3 142 | }); 143 | ``` 144 | 145 | 上面的查询只会对具有 active profile 的用户进行计数,因为在将 where 语句添加到 include 时,`required` 被隐式设置为 true。 146 | 147 | 传递给 `findAndCountAll` 的 options 对象与 `findAll` 相同(如下所述)。 148 | 149 | ### `findAll` - 搜索数据库中的多个元素 150 | ```js 151 | // 找到多个条目 152 | Project.findAll().then(projects => { 153 | // projects 将是所有 Project 实例的数组 154 | }) 155 | 156 | // 也可以: 157 | Project.all().then(projects => { 158 | // projects 将是所有 Project 实例的数组 159 | }) 160 | 161 | // 搜索特定属性 - 使用哈希 162 | Project.findAll({ where: { name: 'A Project' } }).then(projects => { 163 | // projects将是一个具有指定 name 的 Project 实例数组 164 | }) 165 | 166 | // 在特定范围内进行搜索 167 | Project.findAll({ where: { id: [1,2,3] } }).then(projects => { 168 | // projects将是一系列具有 id 1,2 或 3 的项目 169 | // 这实际上是在做一个 IN 查询 170 | }) 171 | 172 | Project.findAll({ 173 | where: { 174 | id: { 175 | [Op.and]: {a: 5}, // 且 (a = 5) 176 | [Op.or]: [{a: 5}, {a: 6}], // (a = 5 或 a = 6) 177 | [Op.gt]: 6, // id > 6 178 | [Op.gte]: 6, // id >= 6 179 | [Op.lt]: 10, // id < 10 180 | [Op.lte]: 10, // id <= 10 181 | [Op.ne]: 20, // id != 20 182 | [Op.between]: [6, 10], // 在 6 和 10 之间 183 | [Op.notBetween]: [11, 15], // 不在 11 和 15 之间 184 | [Op.in]: [1, 2], // 在 [1, 2] 之中 185 | [Op.notIn]: [1, 2], // 不在 [1, 2] 之中 186 | [Op.like]: '%hat', // 包含 '%hat' 187 | [Op.notLike]: '%hat', // 不包含 '%hat' 188 | [Op.iLike]: '%hat', // 包含 '%hat' (不区分大小写) (仅限 PG) 189 | [Op.notILike]: '%hat', // 不包含 '%hat' (仅限 PG) 190 | [Op.overlap]: [1, 2], // && [1, 2] (PG数组重叠运算符) 191 | [Op.contains]: [1, 2], // @> [1, 2] (PG数组包含运算符) 192 | [Op.contained]: [1, 2], // <@ [1, 2] (PG数组包含于运算符) 193 | [Op.any]: [2,3], // 任何数组[2, 3]::INTEGER (仅限 PG) 194 | }, 195 | status: { 196 | [Op.not]: false, // status 不为 FALSE 197 | } 198 | } 199 | }) 200 | ``` 201 | 202 | ### 复合过滤 / OR / NOT 查询 203 | 204 | 你可以使用多层嵌套的 AND,OR 和 NOT 条件进行一个复合的 where 查询。 为了做到这一点,你可以使用 `or` , `and` 或 `not` `运算符`: 205 | 206 | 207 | ```js 208 | Project.findOne({ 209 | where: { 210 | name: 'a project', 211 | [Op.or]: [ 212 | { id: [1,2,3] }, 213 | { id: { [Op.gt]: 10 } } 214 | ] 215 | } 216 | }) 217 | 218 | Project.findOne({ 219 | where: { 220 | name: 'a project', 221 | id: { 222 | [Op.or]: [ 223 | [1,2,3], 224 | { [Op.gt]: 10 } 225 | ] 226 | } 227 | } 228 | }) 229 | ``` 230 | 231 | 这两段代码将生成以下内容: 232 | 233 | ```sql 234 | SELECT * 235 | FROM `Projects` 236 | WHERE ( 237 | `Projects`.`name` = 'a project' 238 | AND (`Projects`.`id` IN (1,2,3) OR `Projects`.`id` > 10) 239 | ) 240 | LIMIT 1; 241 | ``` 242 | 243 | `not` 示例: 244 | 245 | ```js 246 | Project.findOne({ 247 | where: { 248 | name: 'a project', 249 | [Op.not]: [ 250 | { id: [1,2,3] }, 251 | { array: { [Op.contains]: [3,4,5] } } 252 | ] 253 | } 254 | }); 255 | ``` 256 | 257 | 将生成: 258 | 259 | ```sql 260 | SELECT * 261 | FROM `Projects` 262 | WHERE ( 263 | `Projects`.`name` = 'a project' 264 | AND NOT (`Projects`.`id` IN (1,2,3) OR `Projects`.`array` @> ARRAY[3,4,5]::INTEGER[]) 265 | ) 266 | LIMIT 1; 267 | ``` 268 | 269 | ### 用限制,偏移,顺序和分组操作数据集 270 | 271 | 要获取更多相关数据,可以使用限制,偏移,顺序和分组: 272 | 273 | ```js 274 | // 限制查询的结果 275 | Project.findAll({ limit: 10 }) 276 | 277 | // 跳过前10个元素 278 | Project.findAll({ offset: 10 }) 279 | 280 | // 跳过前10个元素,并获取2个 281 | Project.findAll({ offset: 10, limit: 2 }) 282 | ``` 283 | 284 | 分组和排序的语法是相同的,所以下面只用一个单独的例子来解释分组,而其余的则是排序。 您下面看到的所有内容也可以对分组进行 285 | 286 | ```js 287 | Project.findAll({order: 'title DESC'}) 288 | // 生成 ORDER BY title DESC 289 | 290 | Project.findAll({group: 'name'}) 291 | // 生成 GROUP BY name 292 | ``` 293 | 294 | 请注意,在上述两个示例中,提供的字符串逐字插入到查询中,所以不会转义列名称。 当你向 order / group 提供字符串时,将始终如此。 如果要转义列名,您应该提供一个参数数组,即使您只想通过单个列进行 order / group 295 | 296 | ```js 297 | something.findOne({ 298 | order: [ 299 | // 将返回 `name` 300 | ['name'], 301 | // 将返回 `username` DESC 302 | ['username', 'DESC'], 303 | // 将返回 max(`age`) 304 | sequelize.fn('max', sequelize.col('age')), 305 | // 将返回 max(`age`) DESC 306 | [sequelize.fn('max', sequelize.col('age')), 'DESC'], 307 | // 将返回 otherfunction(`col1`, 12, 'lalala') DESC 308 | [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], 309 | // 将返回 otherfunction(awesomefunction(`col`)) DESC,这个嵌套是可以无限的! 310 | [sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC'] 311 | ] 312 | }) 313 | ``` 314 | 315 | 回顾一下,order / group数组的元素可以是以下内容: 316 | 317 | * String - 将被引用 318 | * Array - 第一个元素将被引用,第二个将被逐字地追加 319 | * Object - 320 | * raw 将被添加逐字引用 321 | * 如果未设置 raw,一切都被忽略,查询将失败 322 | * Sequelize.fn 和 Sequelize.col 返回函数和引用的列 323 | 324 | ### 原始查询 325 | 326 | 有时候,你可能会期待一个你想要显示的大量数据集,而无需操作。 对于你选择的每一行,Sequelize 创建一个具有更新,删除和获取关联等功能的实例。如果您有数千行,则可能需要一些时间。 如果您只需要原始数据,并且不想更新任何内容,您可以这样做来获取原始数据。 327 | 328 | ```js 329 | // 你期望从数据库的一个巨大的数据集, 330 | // 并且不想花时间为每个条目构建DAO? 331 | // 您可以传递一个额外的查询参数来取代原始数据: 332 | Project.findAll({ where: { ... }, raw: true }) 333 | ``` 334 | 335 | ### `count` - 计算数据库中元素的出现次数 336 | 337 | 还有一种数据库对象计数的方法: 338 | 339 | ```js 340 | Project.count().then(c => { 341 | console.log("There are " + c + " projects!") 342 | }) 343 | 344 | Project.count({ where: {'id': {[Op.gt]: 25}} }).then(c => { 345 | console.log("There are " + c + " projects with an id greater than 25.") 346 | }) 347 | ``` 348 | 349 | ### `max` - 获取特定表中特定属性的最大值 350 | 351 | 这里是获取属性的最大值的方法: 352 | 353 | ```js 354 | /* 355 |    我们假设3个具有属性年龄的对象。 356 |    第一个是10岁, 357 |    第二个是5岁, 358 |    第三个是40岁。 359 | */ 360 | Project.max('age').then(max => { 361 | // 将返回 40 362 | }) 363 | 364 | Project.max('age', { where: { age: { [Op.lt]: 20 } } }).then(max => { 365 | // 将会是 10 366 | }) 367 | ``` 368 | 369 | ### `min` - 获取特定表中特定属性的最小值 370 | 371 | 这里是获取属性的最小值的方法: 372 | 373 | ```js 374 | /* 375 |    我们假设3个具有属性年龄的对象。 376 |    第一个是10岁, 377 |    第二个是5岁, 378 |    第三个是40岁。 379 | */ 380 | Project.min('age').then(min => { 381 | // 将返回 5 382 | }) 383 | 384 | Project.min('age', { where: { age: { [Op.gt]: 5 } } }).then(min => { 385 | // 将会是 10 386 | }) 387 | ``` 388 | 389 | ### `sum` - 特定属性的值求和 390 | 391 | 为了计算表的特定列的总和,可以使用“sum”方法。 392 | 393 | ```js 394 | /* 395 |    我们假设3个具有属性年龄的对象。 396 |    第一个是10岁, 397 |    第二个是5岁, 398 |    第三个是40岁。 399 | */ 400 | Project.sum('age').then(sum => { 401 | // 将返回 55 402 | }) 403 | 404 | Project.sum('age', { where: { age: { [Op.gt]: 5 } } }).then(sum => { 405 | // 将会是 50 406 | }) 407 | ``` 408 | 409 | ## 预加载 410 | 411 | 当你从数据库检索数据时,也想同时获得与之相关联的查询,这被称为预加载。这个基本思路就是当你调用 `find` 或 `findAll` 时使用 `include` 属性。让我们假设以下设置: 412 | 413 | ```js 414 | const User = sequelize.define('user', { name: Sequelize.STRING }) 415 | const Task = sequelize.define('task', { name: Sequelize.STRING }) 416 | const Tool = sequelize.define('tool', { name: Sequelize.STRING }) 417 | 418 | Task.belongsTo(User) 419 | User.hasMany(Task) 420 | User.hasMany(Tool, { as: 'Instruments' }) 421 | 422 | sequelize.sync().then(() => { 423 | // 这是我们继续的地方 ... 424 | }) 425 | ``` 426 | 427 | 首先,让我们用它们的关联 user 加载所有的 task。 428 | 429 | ```js 430 | Task.findAll({ include: [ User ] }).then(tasks => { 431 | console.log(JSON.stringify(tasks)) 432 | 433 | /* 434 | [{ 435 | "name": "A Task", 436 | "id": 1, 437 | "createdAt": "2013-03-20T20:31:40.000Z", 438 | "updatedAt": "2013-03-20T20:31:40.000Z", 439 | "userId": 1, 440 | "user": { 441 | "name": "John Doe", 442 | "id": 1, 443 | "createdAt": "2013-03-20T20:31:45.000Z", 444 | "updatedAt": "2013-03-20T20:31:45.000Z" 445 | } 446 | }] 447 | */ 448 | }) 449 | ``` 450 | 451 | 请注意,访问者(结果实例中的 `User` 属性)是单数形式,因为关联是一对一的。 452 | 453 | 接下来的事情:用多对一的关联加载数据! 454 | 455 | ```js 456 | User.findAll({ include: [ Task ] }).then(users => { 457 | console.log(JSON.stringify(users)) 458 | 459 | /* 460 | [{ 461 | "name": "John Doe", 462 | "id": 1, 463 | "createdAt": "2013-03-20T20:31:45.000Z", 464 | "updatedAt": "2013-03-20T20:31:45.000Z", 465 | "tasks": [{ 466 | "name": "A Task", 467 | "id": 1, 468 | "createdAt": "2013-03-20T20:31:40.000Z", 469 | "updatedAt": "2013-03-20T20:31:40.000Z", 470 | "userId": 1 471 | }] 472 | }] 473 | */ 474 | }) 475 | ``` 476 | 477 | 请注意,访问者(结果实例中的 `Tasks` 属性)是复数形式,因为关联是多对一的。 478 | 479 | 如果关联是别名的(使用 `as` 参数),则在包含模型时必须指定此别名。 注意用户的 `Tool` 如何被别名为 `Instruments`。 为了获得正确的权限,您必须指定要加载的模型以及别名: 480 | 481 | ```js 482 | User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => { 483 | console.log(JSON.stringify(users)) 484 | 485 | /* 486 | [{ 487 | "name": "John Doe", 488 | "id": 1, 489 | "createdAt": "2013-03-20T20:31:45.000Z", 490 | "updatedAt": "2013-03-20T20:31:45.000Z", 491 | "Instruments": [{ 492 | "name": "Toothpick", 493 | "id": 1, 494 | "createdAt": null, 495 | "updatedAt": null, 496 | "userId": 1 497 | }] 498 | }] 499 | */ 500 | }) 501 | ``` 502 | 503 | 您还可以通过指定与关联别名匹配的字符串来包含别名: 504 | 505 | ```js 506 | User.findAll({ include: ['Instruments'] }).then(users => { 507 | console.log(JSON.stringify(users)) 508 | 509 | /* 510 | [{ 511 | "name": "John Doe", 512 | "id": 1, 513 | "createdAt": "2013-03-20T20:31:45.000Z", 514 | "updatedAt": "2013-03-20T20:31:45.000Z", 515 | "Instruments": [{ 516 | "name": "Toothpick", 517 | "id": 1, 518 | "createdAt": null, 519 | "updatedAt": null, 520 | "userId": 1 521 | }] 522 | }] 523 | */ 524 | }) 525 | 526 | User.findAll({ include: [{ association: 'Instruments' }] }).then(users => { 527 | console.log(JSON.stringify(users)) 528 | 529 | /* 530 | [{ 531 | "name": "John Doe", 532 | "id": 1, 533 | "createdAt": "2013-03-20T20:31:45.000Z", 534 | "updatedAt": "2013-03-20T20:31:45.000Z", 535 | "Instruments": [{ 536 | "name": "Toothpick", 537 | "id": 1, 538 | "createdAt": null, 539 | "updatedAt": null, 540 | "userId": 1 541 | }] 542 | }] 543 | */ 544 | }) 545 | ``` 546 | 547 | 当预加载时,我们也可以使用 `where` 过滤关联的模型。 这将返回 `Tool` 模型中所有与 `where` 语句匹配的行的`User`。 548 | 549 | ```js 550 | User.findAll({ 551 | include: [{ 552 | model: Tool, 553 | as: 'Instruments', 554 | where: { name: { [Op.like]: '%ooth%' } } 555 | }] 556 | }).then(users => { 557 | console.log(JSON.stringify(users)) 558 | 559 | /* 560 | [{ 561 | "name": "John Doe", 562 | "id": 1, 563 | "createdAt": "2013-03-20T20:31:45.000Z", 564 | "updatedAt": "2013-03-20T20:31:45.000Z", 565 | "Instruments": [{ 566 | "name": "Toothpick", 567 | "id": 1, 568 | "createdAt": null, 569 | "updatedAt": null, 570 | "userId": 1 571 | }] 572 | }], 573 | 574 | [{ 575 | "name": "John Smith", 576 | "id": 2, 577 | "createdAt": "2013-03-20T20:31:45.000Z", 578 | "updatedAt": "2013-03-20T20:31:45.000Z", 579 | "Instruments": [{ 580 | "name": "Toothpick", 581 | "id": 1, 582 | "createdAt": null, 583 | "updatedAt": null, 584 | "userId": 1 585 | }] 586 | }], 587 | */ 588 | }) 589 | ``` 590 | 591 | 当使用 `include.where` 过滤一个预加载的模型时,`include.required` 被隐式设置为 `true`。 这意味着内部联接完成返回具有任何匹配子项的父模型。 592 | 593 | ### 使用预加载模型的顶层 WHERE 594 | 595 | 将模型的 `WHERE` 条件从 `ON` 条件的 include 模式移动到顶层,你可以使用 `'$nested.column$'` 语法: 596 | 597 | ```js 598 | User.findAll({ 599 | where: { 600 | '$Instruments.name$': { [Op.iLike]: '%ooth%' } 601 | }, 602 | include: [{ 603 | model: Tool, 604 | as: 'Instruments' 605 | }] 606 | }).then(users => { 607 | console.log(JSON.stringify(users)); 608 | 609 | /* 610 | [{ 611 | "name": "John Doe", 612 | "id": 1, 613 | "createdAt": "2013-03-20T20:31:45.000Z", 614 | "updatedAt": "2013-03-20T20:31:45.000Z", 615 | "Instruments": [{ 616 | "name": "Toothpick", 617 | "id": 1, 618 | "createdAt": null, 619 | "updatedAt": null, 620 | "userId": 1 621 | }] 622 | }], 623 | 624 | [{ 625 | "name": "John Smith", 626 | "id": 2, 627 | "createdAt": "2013-03-20T20:31:45.000Z", 628 | "updatedAt": "2013-03-20T20:31:45.000Z", 629 | "Instruments": [{ 630 | "name": "Toothpick", 631 | "id": 1, 632 | "createdAt": null, 633 | "updatedAt": null, 634 | "userId": 1 635 | }] 636 | }], 637 | */ 638 | ``` 639 | 640 | ### 包括所有 641 | 642 | 要包含所有属性,您可以使用 `all:true` 传递单个对象: 643 | 644 | ```js 645 | User.findAll({ include: [{ all: true }]}); 646 | ``` 647 | 648 | ### 包括软删除的记录 649 | 650 | 如果想要加载软删除的记录,可以通过将 `include.paranoid` 设置为 `false` 来实现 651 | 652 | ```js 653 | User.findAll({ 654 | include: [{ 655 | model: Tool, 656 | where: { name: { [Op.like]: '%ooth%' } }, 657 | paranoid: false // query and loads the soft deleted records 658 | }] 659 | }); 660 | ``` 661 | 662 | ### 排序预加载关联 663 | 664 | 在一对多关系的情况下。 665 | 666 | ```js 667 | Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] }); 668 | Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] }); 669 | Company.findAll({ 670 | include: [ { model: Division, as: 'Div' } ], 671 | order: [ [ { model: Division, as: 'Div' }, 'name' ] ] 672 | }); 673 | Company.findAll({ 674 | include: [ { model: Division, as: 'Div' } ], 675 | order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ] 676 | }); 677 | Company.findAll({ 678 | include: [ { model: Division, include: [ Department ] } ], 679 | order: [ [ Division, Department, 'name' ] ] 680 | }); 681 | ``` 682 | 683 | 在多对多关系的情况下,您还可以通过表中的属性进行排序。 684 | 685 | ```js 686 | Company.findAll({ 687 | include: [ { model: Division, include: [ Department ] } ], 688 | order: [ [ Division, DepartmentDivision, 'name' ] ] 689 | }); 690 | ``` 691 | 692 | ### 嵌套预加载 693 | 694 | 您可以使用嵌套的预加载来加载相关模型的所有相关模型: 695 | 696 | ```js 697 | User.findAll({ 698 | include: [ 699 | {model: Tool, as: 'Instruments', include: [ 700 | {model: Teacher, include: [ /* etc */]} 701 | ]} 702 | ] 703 | }).then(users => { 704 | console.log(JSON.stringify(users)) 705 | 706 | /* 707 | [{ 708 | "name": "John Doe", 709 | "id": 1, 710 | "createdAt": "2013-03-20T20:31:45.000Z", 711 | "updatedAt": "2013-03-20T20:31:45.000Z", 712 | "Instruments": [{ // 1:M and N:M association 713 | "name": "Toothpick", 714 | "id": 1, 715 | "createdAt": null, 716 | "updatedAt": null, 717 | "userId": 1, 718 | "Teacher": { // 1:1 association 719 | "name": "Jimi Hendrix" 720 | } 721 | }] 722 | }] 723 | */ 724 | }) 725 | ``` 726 | 727 | 这将产生一个外连接。 但是,相关模型上的 `where` 语句将创建一个内部连接,并仅返回具有匹配子模型的实例。 要返回所有父实例,您应该添加 `required: false`。 728 | 729 | ```js 730 | User.findAll({ 731 | include: [{ 732 | model: Tool, 733 | as: 'Instruments', 734 | include: [{ 735 | model: Teacher, 736 | where: { 737 | school: "Woodstock Music School" 738 | }, 739 | required: false 740 | }] 741 | }] 742 | }).then(users => { 743 | /* ... */ 744 | }) 745 | ``` 746 | 747 | 以上查询将返回所有用户及其所有乐器,但只会返回与 `Woodstock Music School` 相关的老师。 748 | 749 | 包括所有也支持嵌套加载: 750 | 751 | ```js 752 | User.findAll({ include: [{ all: true, nested: true }]}); 753 | ``` 754 | -------------------------------------------------------------------------------- /models-definition.md: -------------------------------------------------------------------------------- 1 | # Model definition - 模型定义 2 | 3 | 要定义模型和表之间的映射,请使用`define`方法。 随后Sequelize将自动添加`createdAt`和`updatedAt`属性。 因此,您将能够知道数据库条目何时进入数据库以及最后一次更新时。 如果您不想在模型上使用时间戳,只需要一些时间戳,或者您正在使用现有的数据库,其中列被命名为别的东西,直接跳转到[configuration][0]以查看如何执行此操作。 4 | 5 | 6 | ```js 7 | const Project = sequelize.define('project', { 8 | title: Sequelize.STRING, 9 | description: Sequelize.TEXT 10 | }) 11 | 12 | const Task = sequelize.define('task', { 13 | title: Sequelize.STRING, 14 | description: Sequelize.TEXT, 15 | deadline: Sequelize.DATE 16 | }) 17 | ``` 18 | 19 | 你还可以在每列上进行一些设置: 20 | 21 | ```js 22 | const Foo = sequelize.define('foo', { 23 | // 如果未赋值,则自动设置值为 TRUE 24 | flag: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true}, 25 | 26 | // 设置默认时间为当前时间 27 | myDate: { type: Sequelize.DATE, defaultValue: Sequelize.NOW }, 28 | 29 | // 将allowNull设置为false会将NOT NULL添加到列中, 30 | // 这意味着当列为空时执行查询时将从DB抛出错误。 31 | // 如果要在查询DB之前检查值不为空,请查看下面的验证部分。 32 | title: { type: Sequelize.STRING, allowNull: false}, 33 | 34 | // 创建具有相同值的两个对象将抛出一个错误。 唯一属性可以是布尔值或字符串。 35 | // 如果为多个列提供相同的字符串,则它们将形成复合唯一键。 36 | uniqueOne: { type: Sequelize.STRING, unique: 'compositeIndex'}, 37 | uniqueTwo: { type: Sequelize.INTEGER, unique: 'compositeIndex'}, 38 | 39 | // unique属性用来创建一个唯一约束。 40 | someUnique: {type: Sequelize.STRING, unique: true}, 41 | // 这与在模型选项中创建索引完全相同。 42 | {someUnique: {type: Sequelize.STRING}}, 43 | {indexes: [{unique: true, fields: ['someUnique']}]}, 44 | 45 | // primaryKey用于定义主键。 46 | identifier: { type: Sequelize.STRING, primaryKey: true}, 47 | 48 | // autoIncrement可用于创建自增的整数列 49 | incrementMe: { type: Sequelize.INTEGER, autoIncrement: true }, 50 | 51 | // 你可以通过'field'属性指定自定义字段名称: 52 | fieldWithUnderscores: { type: Sequelize.STRING, field: 'field_with_underscores' }, 53 | 54 | // 这可以创建一个外键: 55 | bar_id: { 56 | type: Sequelize.INTEGER, 57 | 58 | references: { 59 | // 这是引用另一个模型 60 | model: Bar, 61 | 62 | // 这是引用模型的列名称 63 | key: 'id', 64 | 65 | // 这声明什么时候检查外键约束。 仅限PostgreSQL。 66 | deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE 67 | } 68 | } 69 | }) 70 | ``` 71 | 72 | 注释选项也可以在表上使用, 查看 [model configuration][0] 73 | 74 | 75 | ## 时间戳 76 | 77 | 默认情况下,Sequelize 会将 `createdAt` 和 `updatedAt` 属性添加到模型中,以便您能够知道数据库条目何时进入数据库以及何时被更新。 78 | 79 | 请注意,如果您使用 Sequelize 迁移,则需要将 `createdAt` 和 `updatedAt` 字段添加到迁移定义中: 80 | 81 | ```js 82 | module.exports = { 83 | up(queryInterface, Sequelize) { 84 | return queryInterface.createTable('my-table', { 85 | id: { 86 | type: Sequelize.INTEGER, 87 | primaryKey: true, 88 | autoIncrement: true, 89 | }, 90 | 91 | // 时间戳 92 | createdAt: Sequelize.DATE, 93 | updatedAt: Sequelize.DATE, 94 | }) 95 | }, 96 | down(queryInterface, Sequelize) { 97 | return queryInterface.dropTable('my-table'); 98 | }, 99 | } 100 | 101 | ``` 102 | 103 | 如果您不想在模型上使用时间戳,只需要一些时间戳记,或者您正在使用现有的数据库,其中列被命名为别的东西,直接跳转到 [configuration] [0] 以查看如何执行此操作。 104 | 105 | 106 | ## 数据类型 107 | 108 | 以下是 Sequelize 支持的一些数据类型。 有关完整和更新的列表, 参阅 [DataTypes](/variable/index.html#static-variable-DataTypes). 109 | 110 | ```js 111 | Sequelize.STRING // VARCHAR(255) 112 | Sequelize.STRING(1234) // VARCHAR(1234) 113 | Sequelize.STRING.BINARY // VARCHAR BINARY 114 | Sequelize.TEXT // TEXT 115 | Sequelize.TEXT('tiny') // TINYTEXT 116 | 117 | Sequelize.INTEGER // INTEGER 118 | Sequelize.BIGINT // BIGINT 119 | Sequelize.BIGINT(11) // BIGINT(11) 120 | 121 | Sequelize.FLOAT // FLOAT 122 | Sequelize.FLOAT(11) // FLOAT(11) 123 | Sequelize.FLOAT(11, 12) // FLOAT(11,12) 124 | 125 | Sequelize.REAL // REAL PostgreSQL only. 126 | Sequelize.REAL(11) // REAL(11) PostgreSQL only. 127 | Sequelize.REAL(11, 12) // REAL(11,12) PostgreSQL only. 128 | 129 | Sequelize.DOUBLE // DOUBLE 130 | Sequelize.DOUBLE(11) // DOUBLE(11) 131 | Sequelize.DOUBLE(11, 12) // DOUBLE(11,12) 132 | 133 | Sequelize.DECIMAL // DECIMAL 134 | Sequelize.DECIMAL(10, 2) // DECIMAL(10,2) 135 | 136 | Sequelize.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres 137 | Sequelize.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision 138 | Sequelize.DATEONLY // DATE without time. 139 | Sequelize.BOOLEAN // TINYINT(1) 140 | 141 | Sequelize.ENUM('value 1', 'value 2') // An ENUM with allowed values 'value 1' and 'value 2' 142 | Sequelize.ARRAY(Sequelize.TEXT) // Defines an array. PostgreSQL only. 143 | 144 | Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only. 145 | Sequelize.JSONB // JSONB column. PostgreSQL only. 146 | 147 | Sequelize.BLOB // BLOB (bytea for PostgreSQL) 148 | Sequelize.BLOB('tiny') // TINYBLOB (bytea for PostgreSQL. Other options are medium and long) 149 | 150 | Sequelize.UUID // UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically) 151 | 152 | Sequelize.RANGE(Sequelize.INTEGER) // Defines int4range range. PostgreSQL only. 153 | Sequelize.RANGE(Sequelize.BIGINT) // Defined int8range range. PostgreSQL only. 154 | Sequelize.RANGE(Sequelize.DATE) // Defines tstzrange range. PostgreSQL only. 155 | Sequelize.RANGE(Sequelize.DATEONLY) // Defines daterange range. PostgreSQL only. 156 | Sequelize.RANGE(Sequelize.DECIMAL) // Defines numrange range. PostgreSQL only. 157 | 158 | Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // Defines array of tstzrange ranges. PostgreSQL only. 159 | 160 | Sequelize.GEOMETRY // Spatial column. PostgreSQL (with PostGIS) or MySQL only. 161 | Sequelize.GEOMETRY('POINT') // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only. 162 | Sequelize.GEOMETRY('POINT', 4326) // Spatial column with geometry type and SRID. PostgreSQL (with PostGIS) or MySQL only. 163 | ``` 164 | 165 | BLOB数据类型允许您将数据作为字符串和二进制插入。 当您在具有BLOB列的模型上执行find或findAll时,该数据将始终作为二进制返回。 166 | 167 | 如果你正在使用PostgreSQL TIMESTAMP WITHOUT TIMEZONE,您需要将其解析为不同的时区,请使用pg库自己的解析器: 168 | 169 | ```js 170 | require('pg').types.setTypeParser(1114, stringValue => { 171 | return new Date(stringValue + '+0000'); 172 | // 例如UTC偏移。 使用你想要的任何偏移。 173 | }); 174 | ``` 175 | 176 | 除了上述类型之外,integer,bigint,float和double也支持unsigned和zerofill属性,可以按任何顺序组合: 177 | 请注意,这不适用于PostgreSQL! 178 | 179 | ```js 180 | Sequelize.INTEGER.UNSIGNED // INTEGER UNSIGNED 181 | Sequelize.INTEGER(11).UNSIGNED // INTEGER(11) UNSIGNED 182 | Sequelize.INTEGER(11).ZEROFILL // INTEGER(11) ZEROFILL 183 | Sequelize.INTEGER(11).ZEROFILL.UNSIGNED // INTEGER(11) UNSIGNED ZEROFILL 184 | Sequelize.INTEGER(11).UNSIGNED.ZEROFILL // INTEGER(11) UNSIGNED ZEROFILL 185 | ``` 186 | _上面的例子只显示整数,但是可以用bigint和float来完成_ 187 | 188 | 用对象表示法: 189 | 190 | ```js 191 | // 对于枚举: 192 | sequelize.define('model', { 193 | states: { 194 | type: Sequelize.ENUM, 195 | values: ['active', 'pending', 'deleted'] 196 | } 197 | }) 198 | ``` 199 | 200 | ### 范围类型 201 | 202 | 由于范围类型具有其绑定的包含(inclusive)/排除(exclusive)的额外信息,所以使用一个元组在javascript中表示它们并不是很简单。 203 | 204 | 将范围作为值提供时,您可以从以下API中进行选择: 205 | 206 | ```js 207 | // 默认为 '["2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' 208 | // 包含下限, 排除上限 209 | Timeline.create({ range: [new Date(Date.UTC(2016, 0, 1)), new Date(Date.UTC(2016, 1, 1))] }); 210 | 211 | // 控制包含 212 | const range = [new Date(Date.UTC(2016, 0, 1)), new Date(Date.UTC(2016, 1, 1))]; 213 | range.inclusive = false; // '()' 214 | range.inclusive = [false, true]; // '(]' 215 | range.inclusive = true; // '[]' 216 | range.inclusive = [true, false]; // '[)' 217 | 218 | // 或作为单个表达式 219 | const range = [ 220 | { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, 221 | { value: new Date(Date.UTC(2016, 1, 1)), inclusive: true }, 222 | ]; 223 | // '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]' 224 | 225 | // 复合形式 226 | const range = [ 227 | { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, 228 | new Date(Date.UTC(2016, 1, 1)), 229 | ]; 230 | // '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' 231 | 232 | Timeline.create({ range }); 233 | ``` 234 | 235 | 无论怎样, 请注意无论何时你接收到的返回值将会是是一个范围: 236 | 237 | ```js 238 | // 储存的值: ("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"] 239 | range // [Date, Date] 240 | range.inclusive // [false, true] 241 | ``` 242 | 243 | 确保在序列化之前将其转换为可序列化的格式,因为数组额外的属性将不会被序列化。 244 | 245 | #### 特殊情况 246 | 247 | ```js 248 | // 空范围: 249 | Timeline.create({ range: [] }); // range = 'empty' 250 | 251 | // 无限制范围: 252 | Timeline.create({ range: [null, null] }); // range = '[,)' 253 | // range = '[,"2016-01-01 00:00:00+00:00")' 254 | Timeline.create({ range: [null, new Date(Date.UTC(2016, 0, 1))] }); 255 | 256 | // 无穷范围: 257 | // range = '[-infinity,"2016-01-01 00:00:00+00:00")' 258 | Timeline.create({ range: [-Infinity, new Date(Date.UTC(2016, 0, 1))] }); 259 | 260 | ``` 261 | 262 | ## 可延迟 263 | 264 | 当你在 PostgreSQL 中指定外键列的参数来声明成一个可延迟类型。 可用的选项如下: 265 | 266 | ```js 267 | // 将所有外键约束检查推迟到事务结束时。 268 | Sequelize.Deferrable.INITIALLY_DEFERRED 269 | 270 | // 立即检查外键约束。 271 | Sequelize.Deferrable.INITIALLY_IMMEDIATE 272 | 273 | // 不要推迟检查。 274 | Sequelize.Deferrable.NOT 275 | ``` 276 | 277 | 最后一个参数是 PostgreSQL 的默认值,不允许你在事务中动态的更改规则。 查看 [the transaction section](/manual/tutorial/transactions.html#options) 获取补充信息. 278 | 279 | ## Getters & setters 280 | 281 | 可以在模型上定义'对象属性'getter和setter函数,这些可以用于映射到数据库字段的“保护”属性,也可以用于定义“伪”属性。 282 | 283 | Getters和Setters可以通过两种方式定义(您可以混合使用这两种方式): 284 | 285 | * 作为属性定义的一部分 286 | * 作为模型参数的一部分 287 | 288 | **注意:** 如果在两个地方定义了getter或setter,那么在相关属性定义中找到的函数始终是优先的。 289 | 290 | ### 定义为属性定义的一部分 291 | 292 | ```js 293 | const Employee = sequelize.define('employee', { 294 | name: { 295 | type: Sequelize.STRING, 296 | allowNull: false, 297 | get() { 298 | const title = this.getDataValue('title'); 299 | // 'this' 允许你访问实例的属性 300 | return this.getDataValue('name') + ' (' + title + ')'; 301 | }, 302 | }, 303 | title: { 304 | type: Sequelize.STRING, 305 | allowNull: false, 306 | set(val) { 307 | this.setDataValue('title', val.toUpperCase()); 308 | } 309 | } 310 | }); 311 | 312 | Employee 313 | .create({ name: 'John Doe', title: 'senior engineer' }) 314 | .then(employee => { 315 | console.log(employee.get('name')); // John Doe (SENIOR ENGINEER) 316 | console.log(employee.get('title')); // SENIOR ENGINEER 317 | }) 318 | ``` 319 | 320 | ### 定义为模型参数的一部分 321 | 322 | 以下是在模型参数中定义 getter 和 setter 的示例。 `fullName` getter,是一个说明如何在模型上定义伪属性的例子 - 这些属性实际上不是数据库模式的一部分。 事实上,伪属性可以通过两种方式定义:使用模型getter,或者使用[`虚拟`数据类型](/variable/index.html#static-variable-DataTypes)的列。 虚拟数据类型可以有验证,而虚拟属性的getter则不能。 323 | 324 | 请注意,`fullName` getter函数中引用的`this.firstname`和`this.lastname`将触发对相应getter函数的调用。 如果你不想那样使用`getDataValue()`方法来访问原始值(见下文)。 325 | 326 | ```js 327 | const Foo = sequelize.define('foo', { 328 | firstname: Sequelize.STRING, 329 | lastname: Sequelize.STRING 330 | }, { 331 | getterMethods: { 332 | fullName() { 333 | return this.firstname + ' ' + this.lastname 334 | } 335 | }, 336 | 337 | setterMethods: { 338 | fullName(value) { 339 | const names = value.split(' '); 340 | 341 | this.setDataValue('firstname', names.slice(0, -1).join(' ')); 342 | this.setDataValue('lastname', names.slice(-1).join(' ')); 343 | }, 344 | } 345 | }); 346 | ``` 347 | 348 | ### 用于 getter 和 setter 定义内部的 Helper 方法 349 | 350 | * 检索底层属性值 - 总是使用 `this.getDataValue()` 351 | 352 | ```js 353 | /* 一个用于 'title' 属性的 getter */ 354 | get() { 355 | return this.getDataValue('title') 356 | } 357 | ``` 358 | 359 | * 设置基础属性值 - 总是使用 `this.setDataValue()` 360 | 361 | ```js 362 | /* 一个用于 'title' 属性的 setter */ 363 | set(title) { 364 | this.setDataValue('title', title.toString().toLowerCase()); 365 | } 366 | ``` 367 | 368 | 369 | **注意:** 坚持使用 `setDataValue()` 和 `getDataValue()` 函数(而不是直接访问底层的“数据值”属性)是非常重要的 - 这样做可以保护您的定制getter和setter不受底层模型实现的变化。 370 | 371 | ## 验证 372 | 373 | 模型验证,允许您为模型的每个属性指定格式/内容/继承验证。 374 | 375 | 验证会自动运行在 `create` , `update` 和 `save` 上。 你也可以调用 `validate()` 手动验证一个实例。 376 | 377 | 验证由 [validator.js][3] 实现。 378 | 379 | ```js 380 | const ValidateMe = sequelize.define('foo', { 381 | foo: { 382 | type: Sequelize.STRING, 383 | validate: { 384 | is: ["^[a-z]+$",'i'], // 只允许字母 385 | is: /^[a-z]+$/i, // 与上一个示例相同,使用了真正的正则表达式 386 | not: ["[a-z]",'i'], // 不允许字母 387 | isEmail: true, // 检查邮件格式 (foo@bar.com) 388 | isUrl: true, // 检查连接格式 (http://foo.com) 389 | isIP: true, // 检查 IPv4 (129.89.23.1) 或 IPv6 格式 390 | isIPv4: true, // 检查 IPv4 (129.89.23.1) 格式 391 | isIPv6: true, // 检查 IPv6 格式 392 | isAlpha: true, // 只允许字母 393 | isAlphanumeric: true, // 只允许使用字母数字 394 | isNumeric: true, // 只允许数字 395 | isInt: true, // 检查是否为有效整数 396 | isFloat: true, // 检查是否为有效浮点数 397 | isDecimal: true, // 检查是否为任意数字 398 | isLowercase: true, // 检查是否为小写 399 | isUppercase: true, // 检查是否为大写 400 | notNull: true, // 不允许为空 401 | isNull: true, // 只允许为空 402 | notEmpty: true, // 不允许空字符串 403 | equals: 'specific value', // 只允许一个特定值 404 | contains: 'foo', // 检查是否包含特定的子字符串 405 | notIn: [['foo', 'bar']], // 检查是否值不是其中之一 406 | isIn: [['foo', 'bar']], // 检查是否值是其中之一 407 | notContains: 'bar', // 不允许包含特定的子字符串 408 | len: [2,10], // 只允许长度在2到10之间的值 409 | isUUID: 4, // 只允许uuids 410 | isDate: true, // 只允许日期字符串 411 | isAfter: "2011-11-05", // 只允许在特定日期之后的日期字符串 412 | isBefore: "2011-11-05", // 只允许在特定日期之前的日期字符串 413 | max: 23, // 只允许值 <= 23 414 | min: 23, // 只允许值 >= 23 415 | isCreditCard: true, // 检查有效的信用卡号码 416 | 417 | // 也可以自定义验证: 418 | isEven(value) { 419 | if (parseInt(value) % 2 != 0) { 420 | throw new Error('Only even values are allowed!') 421 | // 我们也在模型的上下文中,所以如果它存在的话, 422 | // this.otherField会得到otherField的值。 423 | } 424 | } 425 | } 426 | } 427 | }); 428 | ``` 429 | 430 | 请注意,如果需要将多个参数传递给内置的验证函数,则要传递的参数必须位于数组中。 但是,如果要传递单个数组参数,例如`isIn`的可接受字符串数组,则将被解释为多个字符串参数,而不是一个数组参数。 要解决这个问题,传递一个单一长度的参数数组,比如`[['one','two']]`。 431 | 432 | 要使用自定义错误消息而不是 validator.js 提供的错误消息,请使用对象而不是纯值或参数数组,例如不需要参数的验证器可以被给定自定义消息: 433 | 434 | ```js 435 | isInt: { 436 | msg: "Must be an integer number of pennies" 437 | } 438 | ``` 439 | 440 | 或者如果还需要传递参数,请添加一个`args`属性: 441 | 442 | ```js 443 | isIn: { 444 | args: [['en', 'zh']], 445 | msg: "Must be English or Chinese" 446 | } 447 | ``` 448 | 449 | 当使用自定义验证器函数时,错误消息将是抛出的`Error`对象所持有的任何消息。 450 | 451 | 有关内置验证方法的更多详细信息,请参阅[the validator.js project][3] 。 452 | 453 | **提示: **您还可以为日志记录部分定义自定义函数。 只是传递一个方法。 第一个参数将是记录的字符串。 454 | 455 | ### 验证器 与 `allowNull` 456 | 457 | 如果模型的特定字段设置为允许null(使用`allowNull:true`),并且该值已设置为`null`,则其验证器不会运行。 这意味着你可以有一个字符串字段来验证其长度至少为5个字符,但也允许`null`。 458 | 459 | ### 模型验证 460 | 461 | 验证器也可以在特定字段验证器之后用来定义检查模型。例如,你可以确保`纬度`和`经度`都不设置,或者两者都设置,如果设置了一个而另一个未设置则验证失败。 462 | 463 | 模型验证器方法与模型对象的上下文一起调用,如果它们抛出错误,则认为失败,否则通过。 这与自定义字段特定的验证器一样。 464 | 465 | 所收集的任何错误消息都将与验证结果对象一起放在字段验证错误中,这个错误使用在`validate`参数对象中以失败的验证方法的键来命名。即便在任何一个时刻,每个模型验证方法只能有一个错误消息,它会在数组中显示为单个字符串错误,以最大化与字段错误的一致性。 466 | 467 | 一个例子: 468 | 469 | ```js 470 | const Pub = Sequelize.define('pub', { 471 | name: { type: Sequelize.STRING }, 472 | address: { type: Sequelize.STRING }, 473 | latitude: { 474 | type: Sequelize.INTEGER, 475 | allowNull: true, 476 | defaultValue: null, 477 | validate: { min: -90, max: 90 } 478 | }, 479 | longitude: { 480 | type: Sequelize.INTEGER, 481 | allowNull: true, 482 | defaultValue: null, 483 | validate: { min: -180, max: 180 } 484 | }, 485 | }, { 486 | validate: { 487 | bothCoordsOrNone() { 488 | if ((this.latitude === null) !== (this.longitude === null)) { 489 | throw new Error('Require either both latitude and longitude or neither') 490 | } 491 | } 492 | } 493 | }) 494 | ``` 495 | 496 | 在这种简单情况下,如果给定纬度或经度,而不是同时包含两者,则验证失败。 如果我们尝试构建一个超范围的纬度和经度,那么`raging_bullock_arms.validate()`可能会返回 497 | 498 | ```js 499 | { 500 | 'latitude': ['Invalid number: latitude'], 501 | 'bothCoordsOrNone': ['Require either both latitude and longitude or neither'] 502 | } 503 | ``` 504 | 505 | ## 配置 506 | 507 | 你还可以修改 Sequelize 处理列名称的方式: 508 | 509 | ```js 510 | const Bar = sequelize.define('bar', { /* bla */ }, { 511 | // 不添加时间戳属性 (updatedAt, createdAt) 512 | timestamps: false, 513 | 514 | // 不删除数据库条目,但将新添加的属性deletedAt设置为当前日期(删除完成时)。 515 | // paranoid 只有在启用时间戳时才能工作 516 | paranoid: true, 517 | 518 | // 不使用驼峰样式自动添加属性,而是下划线样式,因此updatedAt将变为updated_at 519 | underscored: true, 520 | 521 | // 禁用修改表名; 默认情况下,sequelize将自动将所有传递的模型名称(define的第一个参数)转换为复数。 如果你不想这样,请设置以下内容 522 | freezeTableName: true, 523 | 524 | // 定义表的名称 525 | tableName: 'my_very_custom_table_name', 526 | 527 | // 启用乐观锁定。 启用时,sequelize将向模型添加版本计数属性, 528 | // 并在保存过时的实例时引发OptimisticLockingError错误。 529 | // 设置为true或具有要用于启用的属性名称的字符串。 530 | version: true 531 | }) 532 | ``` 533 | 534 | 如果你希望sequelize处理时间戳,但只想要其中一部分,或者希望您的时间戳被称为别的东西,则可以单独覆盖每个列: 535 | 536 | ```js 537 | const Foo = sequelize.define('foo', { /* bla */ }, { 538 | // 不要忘记启用时间戳! 539 | timestamps: true, 540 | 541 | // 我不想要 createdAt 542 | createdAt: false, 543 | 544 | // 我想 updateAt 实际上被称为 updateTimestamp 545 | updatedAt: 'updateTimestamp', 546 | 547 | // 并且希望 deletedA t被称为 destroyTime(请记住启用paranoid以使其工作) 548 | deletedAt: 'destroyTime', 549 | paranoid: true 550 | }) 551 | ``` 552 | 553 | 您也可以更改数据库引擎,例如 变更到到MyISAM, 默认值是InnoDB。 554 | 555 | ```js 556 | const Person = sequelize.define('person', { /* attributes */ }, { 557 | engine: 'MYISAM' 558 | }) 559 | 560 | // 或全局的 561 | const sequelize = new Sequelize(db, user, pw, { 562 | define: { engine: 'MYISAM' } 563 | }) 564 | ``` 565 | 566 | 最后,您可以为MySQL和PG中的表指定注释 567 | 568 | ```js 569 | const Person = sequelize.define('person', { /* attributes */ }, { 570 | comment: "I'm a table comment!" 571 | }) 572 | ``` 573 | 574 | ## 导入 575 | 576 | 您还可以使用`import`方法将模型定义存储在单个文件中。 返回的对象与导入文件的功能中定义的完全相同。 由于Sequelize`v1:5.0`的导入是被缓存的,所以当调用文件导入两次或更多次时,不会遇到问题。 577 | 578 | ```js 579 | // 在你的服务器文件中 - 例如 app.js 580 | const Project = sequelize.import(__dirname + "/path/to/models/project") 581 | 582 | // 模型已经在 /path/to/models/project.js 中定义好 583 | // 你可能会注意到,DataTypes与上述相同 584 | module.exports = (sequelize, DataTypes) => { 585 | return sequelize.define("project", { 586 | name: DataTypes.STRING, 587 | description: DataTypes.TEXT 588 | }) 589 | } 590 | ``` 591 | 592 | `import`方法也可以接受回调作为参数。 593 | 594 | ```js 595 | sequelize.import('project', (sequelize, DataTypes) => { 596 | return sequelize.define("project", { 597 | name: DataTypes.STRING, 598 | description: DataTypes.TEXT 599 | }) 600 | }) 601 | ``` 602 | 603 | 这个额外的功能也是有用的, 例如 `Error: Cannot find module` 被抛出,即使 `/path/to/models/project` 看起来是正确的。 一些框架,如 Meteor,重载 `require`,并给出“惊喜”的结果,如: 604 | 605 | ``` 606 | Error: Cannot find module '/home/you/meteorApp/.meteor/local/build/programs/server/app/path/to/models/project.js' 607 | ``` 608 | 609 | 这通过传入Meteor的`require`版本来解决. 所以,虽然这可能会失败 ... 610 | 611 | ```js 612 | const AuthorModel = db.import('./path/to/models/project'); 613 | ``` 614 | ... 这应该是成功的 ... 615 | 616 | ```js 617 | const AuthorModel = db.import('project', require('./path/to/models/project')); 618 | ``` 619 | 620 | 621 | 622 | ## 乐观锁定 623 | 624 | Sequelize 内置支持通过模型实例版本计数的乐观锁定。 625 | 626 | 默认情况下禁用乐观锁定,可以通过在特定模型定义或全局模型配置中将`version`属性设置为true来启用。 有关详细信息,请参阅[模型配置][0]。 627 | 628 | 乐观锁定允许并发访问模型记录以进行编辑,并防止冲突覆盖数据。 它通过检查另一个进程是否已经读取记录而进行更改,并在检测到冲突时抛出一个OptimisticLockError。 629 | 630 | ## 数据库同步 631 | 632 | 当开始一个新的项目时,你还不会有一个数据库结构,并且使用Sequelize你也不需要它。 只需指定您的模型结构,并让库完成其余操作。 目前支持的是创建和删除表: 633 | 634 | ```js 635 | // 创建表: 636 | Project.sync() 637 | Task.sync() 638 | 639 | // 强制创建! 640 | Project.sync({force: true}) // 这将先丢弃表,然后重新创建它 641 | 642 | // 删除表: 643 | Project.drop() 644 | Task.drop() 645 | 646 | // 事件处理: 647 | Project.[sync|drop]().then(() => { 648 | // 好吧...一切都很好! 649 | }).catch(error => { 650 | // oooh,你输入了错误的数据库凭据? 651 | }) 652 | ``` 653 | 654 | 因为同步和删除所有的表可能要写很多行,你也可以让Sequelize来为做这些: 655 | 656 | ```js 657 | // 同步所有尚未在数据库中的模型 658 | sequelize.sync() 659 | 660 | // 强制同步所有模型 661 | sequelize.sync({force: true}) 662 | 663 | // 删除所有表 664 | sequelize.drop() 665 | 666 | // 广播处理: 667 | sequelize.[sync|drop]().then(() => { 668 | // woot woot 669 | }).catch(error => { 670 | // whooops 671 | }) 672 | ``` 673 | 674 | 因为`.sync({ force: true })`是具有破坏性的操作,可以使用`match`参数作为附加的安全检查。 675 | 676 | `match`参数可以通知Sequelize,以便在同步之前匹配正则表达式与数据库名称 - 在测试中使用`force:true`但不使用实时代码的情况下的安全检查。 677 | 678 | ```js 679 | // 只有当数据库名称以'_test'结尾时,才会运行.sync() 680 | sequelize.sync({ force: true, match: /_test$/ }); 681 | ``` 682 | 683 | ## 扩展模型 684 | 685 | Sequelize 模型是ES6类。 您可以轻松添加自定义实例或类级别的方法。 686 | 687 | ```js 688 | const User = sequelize.define('user', { firstname: Sequelize.STRING }); 689 | 690 | // 添加一个类级别的方法 691 | User.classLevelMethod = function() { 692 | return 'foo'; 693 | }; 694 | 695 | // 添加实例级别方法 696 | User.prototype.instanceLevelMethod = function() { 697 | return 'bar'; 698 | }; 699 | ``` 700 | 701 | 当然,您还可以访问实例的数据并生成虚拟的getter: 702 | 703 | ```js 704 | const User = sequelize.define('user', { firstname: Sequelize.STRING, lastname: Sequelize.STRING }); 705 | 706 | User.prototype.getFullname = function() { 707 | return [this.firstname, this.lastname].join(' '); 708 | }; 709 | 710 | // 例子: 711 | User.build({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar' 712 | ``` 713 | 714 | ### 索引 715 | Sequelize支持在 `Model.sync()` 或 `sequelize.sync` 中创建的模型定义中添加索引。 716 | 717 | ```js 718 | sequelize.define('user', {}, { 719 | indexes: [ 720 | // 在 poem 上创建一个唯一索引 721 | { 722 | unique: true, 723 | fields: ['poem'] 724 | }, 725 | 726 | // 在使用 jsonb_path_ops 的 operator 数据上创建一个 gin 索引 727 | { 728 | fields: ['data'], 729 | using: 'gin', 730 | operator: 'jsonb_path_ops' 731 | }, 732 | 733 | // 默认的索引名将是 [table]_[fields] 734 | // 创建多列局部索引 735 | { 736 | name: 'public_by_author', 737 | fields: ['author', 'status'], 738 | where: { 739 | status: 'public' 740 | } 741 | }, 742 | 743 | // 具有有序字段的BTREE索引 744 | { 745 | name: 'title_index', 746 | method: 'BTREE', 747 | fields: ['author', {attribute: 'title', collate: 'en_US', order: 'DESC', length: 5}] 748 | } 749 | ] 750 | }) 751 | ``` 752 | 753 | 754 | [0]: /manual/tutorial/models-definition.html#configuration 755 | [3]: https://github.com/chriso/validator.js 756 | [5]: /docs/final/misc#asynchronicity 757 | [6]: http://bluebirdjs.com/docs/api/spread.html 758 | -------------------------------------------------------------------------------- /associations.md: -------------------------------------------------------------------------------- 1 | # Associations - 关联 2 | 3 | 本部分描述 sequelize 中的各种关联类型。 当调用 `User.hasOne(Project)` 这样的方法时,我们说 `User` 模型(该函数被调用的模型)是 __source__ 而 `Project` 模型(模型被传递为参数)是 __target__ 。 4 | 5 | ## 一对一关联 6 | 7 | 一对一关联是通过单个外键连接的两个模型之间的关联。 8 | 9 | ### BelongsTo 10 | 11 | BelongsTo 关联是在 **source model** 上存在一对一关系的外键的关联。 12 | 13 | 一个简单的例子是 **Player** 通过 player 的外键作为 **Team** 的一部分。 14 | 15 | ```js 16 | const Player = this.sequelize.define('player', {/* attributes */}); 17 | const Team = this.sequelize.define('team', {/* attributes */}); 18 | 19 | Player.belongsTo(Team); // 将向 Team 添加一个 teamId 属性以保存 Team 的主键值 20 | ``` 21 | 22 | #### 外键 23 | 24 | 默认情况下,将从目标模型名称和目标主键名称生成 belongsTo 关系的外键。 25 | 26 | 默认的样式是 `camelCase`,但是如果源模型配置为 `underscored: true` ,那么 foreignKey 将是`snake_case`。 27 | 28 | ```js 29 | const User = this.sequelize.define('user', {/* attributes */}) 30 | const Company = this.sequelize.define('company', {/* attributes */}); 31 | 32 | User.belongsTo(Company); // 将 companyId 添加到 user 33 | 34 | const User = this.sequelize.define('user', {/* attributes */}, {underscored: true}) 35 | const Company = this.sequelize.define('company', { 36 | uuid: { 37 | type: Sequelize.UUID, 38 | primaryKey: true 39 | } 40 | }); 41 | 42 | User.belongsTo(Company); // 将 company_uuid 添加到 user 43 | ``` 44 | 45 | 在已定义 `as` 的情况下,将使用它代替目标模型名称。 46 | 47 | ```js 48 | const User = this.sequelize.define('user', {/* attributes */}) 49 | const UserRole = this.sequelize.define('userRole', {/* attributes */}); 50 | 51 | User.belongsTo(UserRole, {as: 'role'}); // 将 role 添加到 user 而不是 userRole 52 | ``` 53 | 54 | 在所有情况下,默认外键可以用 `foreignKey` 选项覆盖。 55 | 当使用外键选项时,Sequelize 将按原样使用: 56 | 57 | ```js 58 | const User = this.sequelize.define('user', {/* attributes */}) 59 | const Company = this.sequelize.define('company', {/* attributes */}); 60 | 61 | User.belongsTo(Company, {foreignKey: 'fk_company'}); // 将 fk_company 添加到 User 62 | ``` 63 | 64 | #### 目标键 65 | 66 | 目标键是源模型上的外键列指向的目标模型上的列。 默认情况下,belongsTo 关系的目标键将是目标模型的主键。 要定义自定义列,请使用 `targetKey` 选项。 67 | 68 | ```js 69 | const User = this.sequelize.define('user', {/* attributes */}) 70 | const Company = this.sequelize.define('company', {/* attributes */}); 71 | 72 | User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // 添加 fk_companyname 到 User 73 | ``` 74 | 75 | 76 | ### HasOne 77 | 78 | HasOne 关联是在 **target model** 上存在一对一关系的外键的关联。 79 | 80 | ```js 81 | const User = sequelize.define('user', {/* ... */}) 82 | const Project = sequelize.define('project', {/* ... */}) 83 |   84 | // 单向关联 85 | Project.hasOne(User) 86 | 87 | /* 88 | 在此示例中,hasOne 将向 User 模型添加一个 projectId 属性 ! 89 | 此外,Project.prototype 将根据传递给定义的第一个参数获取 getUser 和 setUser 的方法。 90 | 如果启用了 underscore 样式,则添加的属性将是 project_id 而不是 projectId。 91 | 92 | 外键将放在 users 表上。 93 | 94 | 你也可以定义外键,例如 如果您已经有一个现有的数据库并且想要处理它: 95 | */ 96 |   97 | Project.hasOne(User, { foreignKey: 'initiator_id' }) 98 |   99 | /* 100 | 因为Sequelize将使用模型的名称(define的第一个参数)作为访问器方法, 101 | 还可以将特殊选项传递给hasOne: 102 | */ 103 |   104 | Project.hasOne(User, { as: 'Initiator' }) 105 | // 现在你可以获得 Project#getInitiator 和 Project#setInitiator 106 |   107 | // 或者让我们来定义一些自己的参考 108 | const Person = sequelize.define('person', { /* ... */}) 109 |   110 | Person.hasOne(Person, {as: 'Father'}) 111 | // 这会将属性 FatherId 添加到 Person 112 |   113 | // also possible: 114 | Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'}) 115 | // 这将把属性 DadId 添加到 Person 116 |   117 | // 在这两种情况下,你都可以: 118 | Person#setFather 119 | Person#getFather 120 |   121 | // 如果你需要联结表两次,你可以联结同一张表 122 | Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'}); 123 | Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'}); 124 | 125 | Game.belongsTo(Team); 126 | ``` 127 | 128 | 即使它被称为 HasOne 关联,对于大多数1:1关系,您通常需要BelongsTo关联,因为 BelongsTo 将会在 hasOne 将添加到目标的源上添加 foreignKey。 129 | 130 | ### HasOne 和 BelongsTo 之间的区别 131 | 132 | 在Sequelize 1:1关系中可以使用HasOne和BelongsTo进行设置。 它们适用于不同的场景。 让我们用一个例子来研究这个差异。 133 | 134 | 假设我们有两个表可以链接 **Player** 和 **Team** 。 让我们定义他们的模型。 135 | 136 | ```js 137 | const Player = this.sequelize.define('player', {/* attributes */}) 138 | const Team = this.sequelize.define('team', {/* attributes */}); 139 | ``` 140 | 141 | 当我们连接 Sequelize 中的两个模型时,我们可以将它们称为一对 **source** 和 **target** 模型。像这样 142 | 143 | 将 **Player** 作为 **source** 而 **Team** 作为 **target** 144 | 145 | ```js 146 | Player.belongsTo(Team); 147 | //或 148 | Player.hasOne(Team); 149 | ``` 150 | 151 | 将 **Team** 作为 **source** 而 **Player** 作为 **target** 152 | 153 | ```js 154 | Team.belongsTo(Player); 155 | //Or 156 | Team.hasOne(Player); 157 | ``` 158 | 159 | HasOne 和 BelongsTo 将关联键插入到不同的模型中。 HasOne 在 **target** 模型中插入关联键,而 BelongsTo 将关联键插入到 **source** 模型中。 160 | 161 | 下是一个示例,说明了 BelongsTo 和 HasOne 的用法。 162 | 163 | ```js 164 | const Player = this.sequelize.define('player', {/* attributes */}) 165 | const Coach = this.sequelize.define('coach', {/* attributes */}) 166 | const Team = this.sequelize.define('team', {/* attributes */}); 167 | ``` 168 | 169 | 假设我们的 `Player` 模型有关于其团队的信息为 `teamId` 列。 关于每个团队的 `Coach` 的信息作为 `coachId` 列存储在 `Team` 模型中。 这两种情况都需要不同种类的1:1关系,因为外键关系每次出现在不同的模型上。 170 | 171 | 当关于关联的信息存在于 **source** 模型中时,我们可以使用 `belongsTo`。 在这种情况下,`Player` 适用于 `belongsTo`,因为它具有 `teamId` 列。 172 | 173 | ```js 174 | Player.belongsTo(Team) // `teamId` 将被添加到 Player / Source 模型中 175 | ``` 176 | 177 | 当关于关联的信息存在于 **target** 模型中时,我们可以使用 `hasOne`。 在这种情况下, `Coach` 适用于 `hasOne` ,因为 `Team` 模型将其 `Coach` 的信息存储为 `coachId` 字段。 178 | 179 | ```js 180 | Coach.hasOne(Team) // `coachId` 将被添加到 Team / Target 模型中 181 | ``` 182 | 183 | ## 一对多关联 184 | 185 | 一对多关联将一个来源与多个目标连接起来。 而多个目标接到同一个特定的源。 186 | 187 | ```js 188 | const User = sequelize.define('user', {/* ... */}) 189 | const Project = sequelize.define('project', {/* ... */}) 190 |   191 | // 好。 现在,事情变得更加复杂(对用户来说并不真实可见)。 192 | // 首先我们来定义一个 hasMany 关联 193 | Project.hasMany(User, {as: 'Workers'}) 194 | ``` 195 | 196 | 这将添加属性 `projectId` 或 `project_id` 到 User。 Project 的实例将获得访问器 `getWorkers` 和 `setWorkers`。 我们让它保持原样,让它成为单向关联。 197 | 但是我们想要更多! 让我们在下一节中以其他方式定义并创建一个多对多的关联: 198 | 199 | 有时您可能需要在不同的列上关联记录,您可以使用 `sourceKey` 选项: 200 | 201 | ```js 202 | const City = sequelize.define('city', { countryCode: Sequelize.STRING }); 203 | const Country = sequelize.define('country', { isoCode: Sequelize.STRING }); 204 | 205 | // 在这里,我们可以根据国家代码连接国家和城市 206 | Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'}); 207 | City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'}); 208 | ``` 209 | 210 | 211 | ## 多对多关联 212 | 213 | 多对多关联用于将源与多个目标相连接。 此外,目标也可以连接到多个源。 214 | 215 | ```js 216 | Project.belongsToMany(User, {through: 'UserProject'}); 217 | User.belongsToMany(Project, {through: 'UserProject'}); 218 | ``` 219 | 220 | 这将创建一个名为 UserProject 的新模型,具有等效的外键`projectId`和`userId`。 属性是否为`camelcase`取决于由表(在这种情况下为`User`和`Project`)连接的两个模型。 221 | 222 | 定义 `through` 为 **required**。 Sequelize 以前会尝试自动生成名称,但并不总是导致最合乎逻辑的设置。 223 | 224 | 这将添加方法 `getUsers`, `setUsers`, `addUser`,`addUsers` 到 `Project`, 还有 `getProjects`, `setProjects`, `addProject`, 和 `addProjects` 到 `User`. 225 | 226 | 有时,您可能需要在关联中使用它们时重命名模型。 让我们通过使用别名(`as`)选项将 users 定义为 workers 而 projects 定义为t asks。 我们还将手动定义要使用的外键: 227 | 228 | ```js 229 | User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' }) 230 | Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' }) 231 | ``` 232 | 233 | `foreignKey` 将允许你在 **through** 关系中设置 **source model** 键。 234 | 235 | `otherKey` 将允许你在 **through** 关系中设置 **target model** 键。 236 | 237 | ```js 238 | User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'}) 239 | ``` 240 | 241 | 当然你也可以使用 belongsToMany 定义自我引用: 242 | 243 | ```js 244 | Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' }) 245 | // 这将创建存储对象的 ID 的表 PersonChildren。 246 | 247 | ``` 248 | 249 | 如果您想要连接表中的其他属性,则可以在定义关联之前为连接表定义一个模型,然后再说明它应该使用该模型进行连接,而不是创建一个新的关联: 250 | 251 | ```js 252 | const User = sequelize.define('user', {}) 253 | const Project = sequelize.define('project', {}) 254 | const UserProjects = sequelize.define('userProjects', { 255 | status: DataTypes.STRING 256 | }) 257 |   258 | User.belongsToMany(Project, { through: UserProjects }) 259 | Project.belongsToMany(User, { through: UserProjects }) 260 | ``` 261 | 262 | 要向 user 添加一个新 project 并设置其状态,您可以将额外的 `options.through` 传递给 setter,其中包含连接表的属性 263 | 264 | ```js 265 | user.addProject(project, { through: { status: 'started' }}) 266 | ``` 267 | 268 | 默认情况下,上面的代码会将 projectId 和 userId 添加到 UserProjects 表中, _删除任何先前定义的主键属性_ - 表将由两个表的键的组合唯一标识,并且没有其他主键列。 要在 `UserProjects` 模型上强添加一个主键,您可以手动添加它。 269 | 270 | ```js 271 | const UserProjects = sequelize.define('userProjects', { 272 | id: { 273 | type: Sequelize.INTEGER, 274 | primaryKey: true, 275 | autoIncrement: true 276 | }, 277 | status: DataTypes.STRING 278 | }) 279 | ``` 280 | 281 | 使用多对多你可以基于 **through** 关系查询并选择特定属性。 例如通过 **through** 使用`findAll` 282 | 283 | ```js 284 | User.findAll({ 285 | include: [{ 286 | model: Project, 287 | through: { 288 | attributes: ['createdAt', 'startedAt', 'finishedAt'], 289 | where: {completed: true} 290 | } 291 | }] 292 | }); 293 | ``` 294 | 295 | ## 作用域 296 | 297 | 本节涉及关联作用域。 有关关联作用域与相关模型上的作用域的定义,请参阅 [作用域](/manual/tutorial/scopes.html)。 298 | 299 | 关联作用域允许您在关联上放置一个作用域(一套 `get` 和 `create` 的默认属性)。作用域可以放在相关联的模型(关联的target)上,也可以通过表上的 n:m 关系。 300 | 301 | #### 1:m 302 | 303 | 假设我们有表评论,帖子和图像。 一个评论可以通过 `commentable_id` 和 `commentable` 关联到一个图像或一个帖子 - 我们说 Post 和 Image 是 `Commentable` 304 | 305 | ```js 306 | const Comment = this.sequelize.define('comment', { 307 | title: Sequelize.STRING, 308 | commentable: Sequelize.STRING, 309 | commentable_id: Sequelize.INTEGER 310 | }); 311 | 312 | Comment.prototype.getItem = function(options) { 313 | return this['get' + this.get('commentable').substr(0, 1).toUpperCase() + this.get('commentable').substr(1)](options); 314 | }; 315 | 316 | Post.hasMany(this.Comment, { 317 | foreignKey: 'commentable_id', 318 | constraints: false, 319 | scope: { 320 | commentable: 'post' 321 | } 322 | }); 323 | Comment.belongsTo(this.Post, { 324 | foreignKey: 'commentable_id', 325 | constraints: false, 326 | as: 'post' 327 | }); 328 | 329 | Image.hasMany(this.Comment, { 330 | foreignKey: 'commentable_id', 331 | constraints: false, 332 | scope: { 333 | commentable: 'image' 334 | } 335 | }); 336 | Comment.belongsTo(this.Image, { 337 | foreignKey: 'commentable_id', 338 | constraints: false, 339 | as: 'image' 340 | }); 341 | ``` 342 | 343 | `constraints: false,` 禁用引用约束 - 由于 `commentable_id` 列引用了几个表,我们不能添加一个 `REFERENCES` 约束。 请注意,Image - > Comment 和 Post - > Comment 关系分别定义了一个作用域:`commentable: 'image'` 和 `commentable: 'post'`。 使用关联功能时自动应用此作用域: 344 | 345 | ```js 346 | image.getComments() 347 | SELECT * FROM comments WHERE commentable_id = 42 AND commentable = 'image'; 348 | 349 | image.createComment({ 350 | title: 'Awesome!' 351 | }) 352 | INSERT INTO comments (title, commentable_id, commentable) VALUES ('Awesome!', 42, 'image'); 353 | 354 | image.addComment(comment); 355 | UPDATE comments SET commentable_id = 42, commentable = 'image' 356 | ``` 357 | 358 | `Comment` 上的 `getItem` 作用函数完成了图片 - 它只是将`commentable`字符串转换为`getImage`或`getPost`的一个调用,提供一个注释是属于一个帖子还是一个图像的抽象概念。您可以将普通选项对象作为参数传递给 `getItem(options)`,以指定任何条件或包含的位置。 359 | 360 | #### n:m 361 | 362 | 继续多态模型的思路,考虑一个 tag 表 - 一个 item 可以有多个 tag,一个 tag 可以与多个 item 相关。 363 | 364 | 为了简洁起见,该示例仅显示了 Post 模型,但实际上 Tag 与其他几个模型相关。 365 | 366 | ```js 367 | const ItemTag = sequelize.define('item_tag', { 368 | id : { 369 | type: DataTypes.INTEGER, 370 | primaryKey: true, 371 | autoIncrement: true 372 | }, 373 | tag_id: { 374 | type: DataTypes.INTEGER, 375 | unique: 'item_tag_taggable' 376 | }, 377 | taggable: { 378 | type: DataTypes.STRING, 379 | unique: 'item_tag_taggable' 380 | }, 381 | taggable_id: { 382 | type: DataTypes.INTEGER, 383 | unique: 'item_tag_taggable', 384 | references: null 385 | } 386 | }); 387 | const Tag = sequelize.define('tag', { 388 | name: DataTypes.STRING 389 | }); 390 | 391 | Post.belongsToMany(Tag, { 392 | through: { 393 | model: ItemTag, 394 | unique: false, 395 | scope: { 396 | taggable: 'post' 397 | } 398 | }, 399 | foreignKey: 'taggable_id', 400 | constraints: false 401 | }); 402 | Tag.belongsToMany(Post, { 403 | through: { 404 | model: ItemTag, 405 | unique: false 406 | }, 407 | foreignKey: 'tag_id', 408 | constraints: false 409 | }); 410 | ``` 411 | 412 | 请注意,作用域列(`taggable`)现在在 through 模型(`ItemTag`)上。 413 | 414 | 我们还可以定义一个更具限制性的关联,例如,通过应用through 模型(`ItemTag`)和目标模型(`Tag`)的作用域来获取所有挂起的 tag。 415 | 416 | ```js 417 | Post.hasMany(Tag, { 418 | through: { 419 | model: ItemTag, 420 | unique: false, 421 | scope: { 422 | taggable: 'post' 423 | } 424 | }, 425 | scope: { 426 | status: 'pending' 427 | }, 428 | as: 'pendingTags', 429 | foreignKey: 'taggable_id', 430 | constraints: false 431 | }); 432 | 433 | Post.getPendingTags(); 434 | ``` 435 | 436 | ```sql 437 | SELECT `tag`.* INNER JOIN `item_tags` AS `item_tag` 438 | ON `tag`.`id` = `item_tag`.`tagId` 439 | AND `item_tag`.`taggable_id` = 42 440 | AND `item_tag`.`taggable` = 'post' 441 | WHERE (`tag`.`status` = 'pending'); 442 | ``` 443 | 444 | `constraints: false` 禁用 `taggable_id` 列上的引用约束。 因为列是多态的,我们不能说它是 `REFERENCES` 一个特定的表。 445 | 446 | ## 命名策略 447 | 448 | 默认情况下,Sequelize将使用模型名称(传递给`sequelize.define`的名称),以便在关联时使用模型名称。 例如,一个名为`user`的模型会将关联模型的实例中的`get / set / add User`函数和加入一个名为`.user`的属性,而一个名为`User`的模型会添加相同的功能,和一个名为`.User`的属性(注意大写U)。 449 | 450 | 正如我们已经看到的,你可以使用`as`来关联模型。 在单个关联(has one 和 belongs to),别名应该是单数,而对于许多关联(has many)它应该是复数。 Sequelize然后使用[inflection] [0]库将别名转换为其单数形式。 但是,这可能并不总是适用于不规则或非英语单词。 在这种情况下,您可以提供复数和单数形式的别名: 451 | 452 | ```js 453 | User.belongsToMany(Project, { as: { singular: 'task', plural: 'tasks' }}) 454 | // Notice that inflection has no problem singularizing tasks, this is just for illustrative purposes. 455 | ``` 456 | 457 | 如果你知道模型将始终在关联中使用相同的别名,则可以在创建模型时提供它 458 | 459 | ```js 460 | const Project = sequelize.define('project', attributes, { 461 | name: { 462 | singular: 'task', 463 | plural: 'tasks', 464 | } 465 | }) 466 |   467 | User.belongsToMany(Project); 468 | ``` 469 | 470 | 这将为用户实例添加 `add/set/get Tasks` 方法。 471 | 472 | 记住,使用`as`来更改关联的名称也会改变外键的名称。 当使用`as`时,也可以指定外键是最安全的。 473 | 474 | 475 | ```js 476 | Invoice.belongsTo(Subscription) 477 | Subscription.hasMany(Invoice) 478 | ``` 479 | 480 | 不使用 `as`,这会按预期添加 `subscriptionId`。 但是,如果您要发送`Invoice.belongsTo(Subscription, { as: 'TheSubscription' })`,那么您将同时拥有 `subscriptionId` 和 `theSubscriptionId`,因为 sequelize 不够聪明,无法确定调用是相同关系的两面。 `foreignKey` 修正了这个问题; 481 | 482 | ```js 483 | Invoice.belongsTo(Subscription, , { as: 'TheSubscription', foreignKey: 'subscription_id' }) 484 | Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' ) 485 | ``` 486 | 487 | ## 关联对象 488 | 489 | 因为 Sequelize 做了很多神奇的事,所以你必须在设置关联后调用 `Sequelize.sync`。 这样做将允许您进行以下操作: 490 | 491 | ```js 492 | Project.belongsToMany(Task) 493 | Task.belongsToMany(Project) 494 |   495 | Project.create()... 496 | Task.create()... 497 | Task.create()... 498 |   499 | // 保存它们.. 然后: 500 | project.setTasks([task1, task2]).then(() => { 501 | // 已保存! 502 | }) 503 |   504 | // 好的,现在它们已经保存了...我怎么才能得到他们? 505 | project.getTasks().then(associatedTasks => { 506 | // associatedTasks 是一个 tasks 的数组 507 | }) 508 |   509 | // 您还可以将过滤器传递给getter方法。 510 | // 它们与你能传递给常规查找器方法的选项相同。 511 | project.getTasks({ where: 'id > 10' }).then(tasks => { 512 | // id大于10的任务 513 | }) 514 |   515 | // 你也可以仅检索关联对象的某些字段。 516 | project.getTasks({attributes: ['title']}).then(tasks => { 517 | // 使用属性“title”和“id”检索任务 518 | }) 519 | ``` 520 | 521 | 要删除创建的关联,您可以调用set方法而不使用特定的ID: 522 | 523 | ```js 524 | // 删除与 task1 的关联 525 | project.setTasks([task2]).then(associatedTasks => { 526 | // 你将只得到 task2 527 | }) 528 |   529 | // 删除全部 530 | project.setTasks([]).then(associatedTasks => { 531 | // 你将得到空数组 532 | }) 533 |   534 | // 或更直接地删除 535 | project.removeTask(task1).then(() => { 536 | // 什么都没有 537 | }) 538 |   539 | // 然后再次添加它们 540 | project.addTask(task1).then(function() { 541 | // 它们又回来了 542 | }) 543 | ``` 544 | 545 | 反之亦然你当然也可以这样做: 546 | 547 | ```js 548 | // project与task1和task2相关联 549 | task2.setProject(null).then(function() { 550 | // 什么都没有 551 | }) 552 | ``` 553 | 554 | 对于 hasOne/belongsTo 与其基本相同: 555 | 556 | ```js 557 | Task.hasOne(User, {as: "Author"}) 558 | Task#setAuthor(anAuthor) 559 | ``` 560 | 561 | 可以通过两种方式添加与自定义连接表的关系的关联(继续前一章中定义的关联): 562 | 563 | ```js 564 | // 在创建关联之前,通过向对象添加具有连接表模型名称的属性 565 | project.UserProjects = { 566 | status: 'active' 567 | } 568 | u.addProject(project) 569 |   570 | // 或者在添加关联时提供第二个options.through参数,其中包含应该在连接表中的数据 571 | u.addProject(project, { through: { status: 'active' }}) 572 |   573 |   574 | // 关联多个对象时,可以组合上述两个选项。 在这种情况下第二个参数 575 | // 如果没有提供使用的数据将被视为默认对象 576 | project1.UserProjects = { 577 | status: 'inactive' 578 | } 579 |   580 | u.setProjects([project1, project2], { through: { status: 'active' }}) 581 | // 上述代码将对项目1记录无效,并且在连接表中对项目2进行active 582 | ``` 583 | 584 | 当获取具有自定义连接表的关联的数据时,连接表中的数据将作为DAO实例返回: 585 | 586 | ```js 587 | u.getProjects().then(projects => { 588 | const project = projects[0] 589 |   590 | if (project.UserProjects.status === 'active') { 591 | // .. 做点什么 592 |   593 | // 由于这是一个真正的DAO实例,您可以在完成操作之后直接保存它 594 | return project.UserProjects.save() 595 | } 596 | }) 597 | ``` 598 | 599 | 如果您仅需要连接表中的某些属性,则可以提供具有所需属性的数组: 600 | 601 | ```js 602 | // 这将仅从 Projects 表中选择 name,仅从 UserProjects 表中选择status 603 | user.getProjects({ attributes: ['name'], joinTableAttributes: ['status']}) 604 | ``` 605 | 606 | ## 检查关联 607 | 608 | 您还可以检查对象是否已经与另一个对象相关联(仅 n:m)。 这是你怎么做的 609 | 610 | ```js 611 | // 检查对象是否是关联对象之一: 612 | Project.create({ /* */ }).then(project => { 613 | return User.create({ /* */ }).then(user => { 614 | return project.hasUser(user).then(result => { 615 | // 结果是 false 616 | return project.addUser(user).then(() => { 617 | return project.hasUser(user).then(result => { 618 | // 结果是 true 619 | }) 620 | }) 621 | }) 622 | }) 623 | }) 624 |   625 | // 检查所有关联的对象是否如预期的那样: 626 | // 我们假设我们已经有一个项目和两个用户 627 | project.setUsers([user1, user2]).then(() => { 628 | return project.hasUsers([user1]); 629 | }).then(result => { 630 | // 结果是 false 631 | return project.hasUsers([user1, user2]); 632 | }).then(result => { 633 | // 结果是 true 634 | }) 635 | ``` 636 | 637 | ## 外键 638 | 639 | 当您在sequelize模型中创建关联时,将自动创建具有约束的外键引用。 设置如下: 640 | 641 | ```js 642 | const Task = this.sequelize.define('task', { title: Sequelize.STRING }) 643 | const User = this.sequelize.define('user', { username: Sequelize.STRING }) 644 |   645 | User.hasMany(Task) 646 | Task.belongsTo(User) 647 | ``` 648 | 649 | 将生成以下SQL: 650 | 651 | ```sql 652 | CREATE TABLE IF NOT EXISTS `User` ( 653 | `id` INTEGER PRIMARY KEY, 654 | `username` VARCHAR(255) 655 | ); 656 | 657 | CREATE TABLE IF NOT EXISTS `Task` ( 658 | `id` INTEGER PRIMARY KEY, 659 | `title` VARCHAR(255), 660 | `user_id` INTEGER REFERENCES `User` (`id`) ON DELETE SET NULL ON UPDATE CASCADE 661 | ); 662 | ``` 663 | 664 | 在task和user的关系之 中在task上注入`user_id`外键,并将其标记为`User`表的引用。默认情况下,如果引用的用户被删除,`user_id`将被设置为`NULL`,如果更新了用户标识的id,则会被更新。通过将`onUpdate`和`onDelete`选项传递给关联调用,可以覆盖这些选项。验证选项为`RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL`。 665 | 666 | 对于1:1和1:m关联,默认选项为`SET NULL`用于删除, `CASCADE`用于更新。对于n:m,两者的默认值为`CASCADE`。这意味着,如果从n:m关联的一侧删除或更新行,引用该行的连接表中的所有行也将被删除或更新。 667 | 668 | 在表之间添加约束意味着在使用`sequelize.sync`时,必须以特定顺序在数据库中创建表。如果`Task`引用了`User`,则必须先创建`User`表,然后才能创建`Task`表。这有时可能导致循环引用,其中后遗症找不到要同步的顺序。想象一下文件和版本的场景。一个文档可以有多个版本,为方便起见,一个文档可以引用它的当前版本。 669 | 670 | ```js 671 | const Document = this.sequelize.define('document', { 672 | author: Sequelize.STRING 673 | }) 674 | const Version = this.sequelize.define('version', { 675 | timestamp: Sequelize.DATE 676 | }) 677 | 678 | Document.hasMany(Version) // 这将 document_id 添加到版本 679 | Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id'}) // 这将current_version_id添加到文档 680 | ``` 681 | 682 | 但是,上面的代码将导致以下错误: `Cyclic dependency found. 'Document' is dependent of itself. Dependency Chain: Document -> Version => Document`. 683 | 为了减轻这一点,我们可以将 `constraints: false` 传递给其中一个关联: 684 | 685 | ```js 686 | Document.hasMany(Version) 687 | Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id', constraints: false}) 688 | ``` 689 | 690 | 这将允许我们正确地同步表: 691 | 692 | ```sql 693 | CREATE TABLE IF NOT EXISTS `Document` ( 694 | `id` INTEGER PRIMARY KEY, 695 | `author` VARCHAR(255), 696 | `current_version_id` INTEGER 697 | ); 698 | CREATE TABLE IF NOT EXISTS `Version` ( 699 | `id` INTEGER PRIMARY KEY, 700 | `timestamp` DATETIME, 701 | `document_id` INTEGER REFERENCES `Document` (`id`) ON DELETE SET NULL ON UPDATE CASCADE 702 | ); 703 | ``` 704 | 705 | ### 强制执行外键引用而不受约束 706 | 707 | 有时,您可能需要引用另一个表,而不添加任何约束或关联。 在这种情况下,您可以手动将引用属性添加到模式定义,并标记它们之间的关系。 708 | 709 | ```js  710 | // 在我们调用 Trainer.hasMany(series) 之后 Series 有一个 外参考键 trainer_id=Trainer.id 711 | const Series = sequelize.define('series', { 712 | title: DataTypes.STRING, 713 | sub_title: DataTypes.STRING, 714 | description: DataTypes.TEXT, 715 |   716 | // 用 `Trainer` 设置外键关系(hasMany) 717 | trainer_id: { 718 | type: DataTypes.INTEGER, 719 | references: { 720 | model: "trainers", 721 | key: "id" 722 | } 723 | } 724 | }) 725 |   726 | const Trainer = sequelize.define('trainer', { 727 | first_name: DataTypes.STRING, 728 | last_name: DataTypes.STRING 729 | }); 730 |   731 | // 在我们调用 Series.hasOne(Video) 之后 Video 有一个 外参考键 series_id=Series.id 732 | const Video = sequelize.define('video', { 733 | title: DataTypes.STRING, 734 | sequence: DataTypes.INTEGER, 735 | description: DataTypes.TEXT, 736 |   737 | // 用 `Series` 设置关系(hasOne) 738 | series_id: { 739 | type: DataTypes.INTEGER, 740 | references: { 741 | model: Series, // 可以是表示表名称的字符串,也可以是对模型的引用 742 | key: "id" 743 | } 744 | } 745 | }); 746 |   747 | Series.hasOne(Video); 748 | Trainer.hasMany(Series); 749 | ``` 750 | 751 | ## 用关联创建 752 | 753 | 如果所有元素都是新的,则可以在一个步骤中创建具有嵌套关联的实例。 754 | 755 | ### 创建一个 "BelongsTo", "Has Many" 或 "HasOne" 关联的元素 756 | 757 | 考虑以下模型: 758 | 759 | ```js 760 | const Product = this.sequelize.define('product', { 761 | title: Sequelize.STRING 762 | }); 763 | const User = this.sequelize.define('user', { 764 | first_name: Sequelize.STRING, 765 | last_name: Sequelize.STRING 766 | }); 767 | const Address = this.sequelize.define('address', { 768 | type: Sequelize.STRING, 769 | line_1: Sequelize.STRING, 770 | line_2: Sequelize.STRING, 771 | city: Sequelize.STRING, 772 | state: Sequelize.STRING, 773 | zip: Sequelize.STRING, 774 | }); 775 | 776 | Product.User = Product.belongsTo(User); 777 | User.Addresses = User.hasMany(Address); 778 | // 也能用于 `hasOne` 779 | ``` 780 | 781 | 可以通过以下方式在一个步骤中创建一个新的`Product`, `User`和一个或多个`Address`: 782 | 783 | ```js 784 | return Product.create({ 785 | title: 'Chair', 786 | user: { 787 | first_name: 'Mick', 788 | last_name: 'Broadstone', 789 | addresses: [{ 790 | type: 'home', 791 | line_1: '100 Main St.', 792 | city: 'Austin', 793 | state: 'TX', 794 | zip: '78704' 795 | }] 796 | } 797 | }, { 798 | include: [{ 799 | association: Product.User, 800 | include: [ User.Addresses ] 801 | }] 802 | }); 803 | ``` 804 | 805 | 这里,我们的用户模型称为`user`,带小写u - 这意味着对象中的属性也应该是`user`。 如果给`sequelize.define`指定的名称为`User`,对象中的键也应为`User`。 对于`addresses`也是同样的,除了它是一个 `hasMany` 关联的复数。 806 | 807 | ### 用别名创建一个 “BelongsTo” 关联的元素 808 | 809 | 可以将前面的示例扩展为支持关联别名。 810 | 811 | ```js 812 | const Creator = Product.belongsTo(User, {as: 'creator'}); 813 | 814 | return Product.create({ 815 | title: 'Chair', 816 | creator: { 817 | first_name: 'Matt', 818 | last_name: 'Hansen' 819 | } 820 | }, { 821 | include: [ Creator ] 822 | }); 823 | ``` 824 | 825 | ### 创建 “HasMany” 或 “BelongsToMany” 关联的元素 826 | 827 | 我们来介绍将产品与许多标签相关联的功能。 设置模型可能如下所示: 828 | 829 | ```js 830 | const Tag = this.sequelize.define('tag', { 831 | name: Sequelize.STRING 832 | }); 833 | 834 | Product.hasMany(Tag); 835 | // Also works for `belongsToMany`. 836 | ``` 837 | 838 | 现在,我们可以通过以下方式创建具有多个标签的产品: 839 | 840 | ```js 841 | Product.create({ 842 | id: 1, 843 | title: 'Chair', 844 | tags: [ 845 | { name: 'Alpha'}, 846 | { name: 'Beta'} 847 | ] 848 | }, { 849 | include: [ Tag ] 850 | }) 851 | ``` 852 | 853 | 然后,我们可以修改此示例以支持别名: 854 | 855 | ```js 856 | const Categories = Product.hasMany(Tag, {as: 'categories'}); 857 | 858 | Product.create({ 859 | id: 1, 860 | title: 'Chair', 861 | categories: [ 862 | {id: 1, name: 'Alpha'}, 863 | {id: 2, name: 'Beta'} 864 | ] 865 | }, { 866 | include: [{ 867 | model: Categories, 868 | as: 'categories' 869 | }] 870 | }) 871 | ``` 872 | 873 | *** 874 | 875 | [0]: https://www.npmjs.org/package/inflection 876 | --------------------------------------------------------------------------------