10. Migrations 数据迁移
Sequelize v2.0
中引入了一个新的CLI
(命令行工具),就像使用 Git/SVN 管理代码一样,你可以使用迁移功能(Migrations)来跟踪数据库的更改。通过迁移功能,你可以将现有数据库转移到另一个状态,反之亦然。进行迁移时,状态转换会被保存到迁移文件中,这些文件描述了如何进入新状态以及如何恢复更改以恢复到旧状态。
- CLI
- 1.1 CLI安装
- 1.2 引导
- 1.3 创建第一个模型(并迁移)
- 1.4 执行迁移
- 1.5 撤消迁移
- 1.6 创建第一个种子
- 1.7 执行种子
- 1.8 撤消种子
- 1.9 CLI使用帮助
- 高级主题
- 2.1 迁移结构
- 2.2
.sequelizerc
文件 - 2.3 动态配置
- 2.4 使用环境变量
- 2.5 指定方言选项
- 2.6 生产用途
- 2.7 存储
- 2.8 配置连接字符串
- 2.9 使用
SSL
连接 - 2.10 程序化使用
queryInterface
对象及功能
迁移需要使用Sequelize CLI,CLI提供了对迁移和项目引导的支持。
1. CLI
1.1 CLI安装
要使用CLI首先要安装相应的包:
$ npm install --save sequelize-cli
当通过-g
参数全局安装npm包时,可以在命令行中通过sequelize [command]
命令来使用CLI。而没有使全局安装时,就可以通过node_modules/.bin/sequelize [command]
来使用CLI。
1.2 引导
在上一步中,我们创建了一个空项目,这里需要执行init
命令:
$ node_modules/.bin/sequelize init
以上命令会创建以下文件夹:
config
:包含配置文件,这些文件会告诉CLI怎样连接数据库models
:包含项目中的所有模型(Model)migrations
:包含所有迁移文件seeders
:包含所有种子文件
注意:以上命令使用到sequelize
模块,如果你项目中没有请使用npm
命令安装;另外,还需要安装对应的方言模块,以实现底层的数据库操作,如:使用MySQL数据库时就需要安装mysql2
模块。
配置
继续后续操作前,我们需要使CLI能够连接到数据库,可以在config/config.json
文件中配置数据库。该文件类似如下:
{ "development": { "username": "root", "password": null, "database": "database_development", "host": "127.0.0.1", "dialect": "mysql" }, "test": { "username": "root", "password": null, "database": "database_test", "host": "127.0.0.1", "dialect": "mysql" }, "production": { "username": "root", "password": null, "database": "database_production", "host": "127.0.0.1", "dialect": "mysql" } }
编辑该文件,正确设置数据库的验证信息和方言(数据库类型)。
注意:如果你配置的数据库还不存在,调用db:create
命令即可自动创建。
1.3 创建第一个模型(并迁移)
正确配置CLI的配置文件后,就可以创建你的第一个迁移。可以通过一个命令来轻松的完成这一操作。
创建模型使用model:generate
命令,该命令包含以下两个参数:
name
-模型名attributes
-模型属性列表
接下来我们来创建一个名为User
的模型:
node_modules/.bin/sequelize model:generate --name User --attributes firstName:string,lastName:string,email:string
以上命令会做以下两件事:
- 在
models
文件夹下创建模型文件user
- 在
migrations
文件夹下创建名称类似XXXXXXXXXXXXXX-create-user.js
的迁移文件
注意:Sequelize只会使用模型文件,模型文件是对数据库中表的表示;迁移文件是对该模型的更改,或者说是CLI所要使用的表;而“迁移”可以认为对数据库修改的一次提交或日志。
1.4 执行迁移
到目前为目,我们尚未在数据库中插入任何内容。刚刚我们为第一个模型User
创建了所需的模型和迁移文件。
现在要在数据库中创建该表,这时需要执行db:migrate
命令:
$ node_modules/.bin/sequelize db:migrate
以上操作会执行以下步骤:
- 确保数据库中有一个名为
SequelizeMeta
的表,此表用于记录在当前数据库上运行的迁移 - 查找尚示执行的迁移文件,这一步通过
SequelizeMeta
表来实现。在本例中,将执行上一步中创建的XXXXXXXXXXXXXX-create-user.js
迁移文件。 - 创建一个名为
Users
的表,其中包含迁移文件中指定的所有列。
1.5 撤消迁移
现在我们的表已在数据库中创建并保存。通过迁移功能,只需运行命令即可恢复到旧状态。
撤消迁移可以使用db:migrate:undo
命令,此命令将还原到最近的迁移:
$ node_modules/.bin/sequelize db:migrate:undo
此外,可以通过db:migrate:undo:all
命令来撤消所有迁移来恢复到初始状态。还可以通过--to
选项来传递迁移文件名称,以恢复到特定迁移。
$ node_modules/.bin/sequelize db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js
1.6 创建第一个种子
有些情况下我们可能需要在一个表中插入一些默认数据。如:在前面的User
表中创建一个演示用户。
要管理所有数据迁移,可以使用seeders
。种子文件表示数据的一些变化,可用于使用样本数据或测试数据填充数据库表。
创建种子文件可以使用seed:generate
命令。现在我们创建一个种子文件,它会添加一个演示用户到User
表中:
$ node_modules/.bin/sequelize seed:generate --name demo-user
这个命令会在seeders
文件夹中创建一个种子文件,文件名类似XXXXXXXXXXXXXX-demo-user.js
。它遵循与迁移文件相同的up/down
语义。
现在编辑这个文件,以添加一个演示用户到User
表:
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.bulkInsert('Users', [{ firstName: 'John', lastName: 'Doe', email: 'demo@demo.com' }], {}); }, down: (queryInterface, Sequelize) => { return queryInterface.bulkDelete('Users', null, {}); } };
1.7 执行种子
上一步我们创建了种子文件,但它还未提交到数据库中。这时可以通过db:seed:all
命令实现:
$ node_modules/.bin/sequelize db:seed:all
以上命令会执行种子文件,演示用户也会被添加到User
表中。
注意:种子执行不像迁移,它不会存储在任何位置(迁移会存储在SequelizeMeta
中),如果需要存储请参阅Storage一节。
1.8 撤消种子
使用种子存储后,种子的执行可以撤消。可以通过以下两个命令实现:
db:seed:undo
命令撤消最近的一次种子操作:
node_modules/.bin/sequelize db:seed:undo
db:seed:undo:all
命令撤消所有种子操作:
node_modules/.bin/sequelize db:seed:undo:all
1.9 CLI使用帮助
可以通过sequelize help
查看使用帮助,该命令会输出当前运行环境中的Node版本、CLI版本、ORM版本及当前版本CLI所支持的命令。
类似如下:
Sequelize CLI [Node: 10.14.2, CLI: 5.4.0, ORM: 4.42.0] sequelize [命令] 命令: sequelize db:migrate 运行待执行的迁移 sequelize db:migrate:schema:timestamps:add 更新迁移表以获取时间戳 sequelize db:migrate:status 列出所有迁移的状态 sequelize db:migrate:undo 恢复迁移 sequelize db:migrate:undo:all 恢复所有迁移 sequelize db:seed 运行指定的种子 sequelize db:seed:undo 撤消最近执行的种子 sequelize db:seed:all 运行所有种子 sequelize db:seed:undo:all 撤消所有已执行的种子 sequelize db:create 创建配置中指定的数据库 sequelize db:drop 删除配置中指定的数据库 sequelize init 初始化项目 sequelize init:config 初始化配置 sequelize init:migrations 初始化迁移 sequelize init:models 初始化模型 sequelize init:seeders 初始化种子 sequelize migration:generate 生成新的迁移文件 [aliases: migration:create] sequelize model:generate 生成一个模模型及期迁移文件 [aliases: model:create] sequelize seed:generate 生成一个新的种子文件 [aliases: seed:create] 选项: --help 显示帮助信息 [布尔] --version 显示版本号 [布尔]
2. 高级主题
2.1 迁移结构
所有迁移都在项目顶层一个名为migrations
的目录中。
sequelize
会生成迁移的结构,以下是一个典型的迁移文件结构:
module.exports = { up: function(queryInterface, Sequelize) { // 转换为新状态的逻辑 }, down: function(queryInterface, Sequelize) { // 恢复修改的逻辑 } }
通过queryInterface
对象可用来修改数据库。Sequelize
对象中存储了数据类型,如:STRING
和INTEGER
。up
和down
函数需要返回一个Promise
。以下是一些示例代码:
module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Person', { name: Sequelize.STRING, isBetaMember: { type: Sequelize.BOOLEAN, defaultValue: false, allowNull: false } }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Person'); } }
可以通过queryInterface
了节查看该对象所支持的方法。
2.2 .sequelizerc
文件
.sequelizerc
文件一个特殊的配置文件,它允许你指定通常作为参数传递给CLI的各种选项。可以使用它的一些场景:
- 需要重写默认的
migrations
,models
,seeders
或config
文件夹。 - 需要重命名
config.json
时。如:重命名为database.json
或其它。
除以上说明外,还有更多的应用场景。接下来,我们来看看如何使用这个文件来进行自定义配置。
首先,在项目根目录下创建.sequelizerc
文件:
$ touch .sequelizerc
以下是一个示例配置:
const path = require('path'); module.exports = { 'config': path.resolve('config', 'database.json'), 'models-path': path.resolve('db', 'models'), 'seeders-path': path.resolve('db', 'seeders'), 'migrations-path': path.resolve('db', 'migrations') }
在这个配置中我们告诉CLI:
- 使用
config/database.json
进行配置设置 - 使用
db/models
做为模型目录 - 使用
db/seeders
做为种子目录 - 使用
db/migrations
做为迁移目录
2.3 动态配置
配置文件默认是一个名为config.json
的JSON文件,但有时你想执行一些代码或访问环境变量,这些操作在JSON文件中是不可能实现的。
Sequelize CLI可以从JSON
或JS
文件中读取配置,这可以通过.sequelizerc
文件配置。
要使用JS
格式的文件做为配置文件,可以在.sequelizerc
文件中配置如下:
const path = require('path'); module.exports = { 'config': path.resolve('config', 'config.js') }
现在Sequelize CLI会加载config/config.js
文件获取配置选项,这样通过这个JS文件可以执行任何代码并导出最终的动态配置文件。示例如下:
const fs = require('fs'); module.exports = { development: { username: 'database_dev', password: 'database_dev', database: 'database_dev', host: '127.0.0.1', dialect: 'mysql' }, test: { username: 'database_test', password: null, database: 'database_test', host: '127.0.0.1', dialect: 'mysql' }, production: { username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, host: process.env.DB_HOSTNAME, dialect: 'mysql', dialectOptions: { ssl: { ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') } } } };
2.4 使用环境变量
使用CLI,可以直接访问config/config.js
中的环境变量。
如下所示,可以在该文件中直接使用环境变量并导出:
module.exports = { development: { username: 'database_dev', password: 'database_dev', database: 'database_dev', host: '127.0.0.1', dialect: 'mysql' }, test: { username: process.env.CI_DB_USERNAME, password: process.env.CI_DB_PASSWORD, database: process.env.CI_DB_NAME, host: '127.0.0.1', dialect: 'mysql' }, production: { username: process.env.PROD_DB_USERNAME, password: process.env.PROD_DB_PASSWORD, database: process.env.PROD_DB_NAME, host: process.env.PROD_DB_HOSTNAME, dialect: 'mysql' }
2.5 指定方言选项
有时你需要指定dialectOption选项,一般你可以在config/config.json
中添加它。而当需要使用代码来获取该选项时,可以使用动态配置文件来处理。
{ "production": { "dialect":"mysql", "dialectOptions": { "bigNumberStrings": true } } }
备注:Sequelize中方言(dialect
)选项用于指定所使用的数据库型。
2.6 生产用途
以下是一些有关在生产环境中使用CLI和迁移设置的提示。
1)使用环境变量进行配置设置,使用动态配置可以更好地实现。以下是一个在生产环境中使用安全配置的设置示例:
const fs = require('fs'); module.exports = { development: { username: 'database_dev', password: 'database_dev', database: 'database_dev', host: '127.0.0.1', dialect: 'mysql' }, test: { username: 'database_test', password: null, database: 'database_test', host: '127.0.0.1', dialect: 'mysql' }, production: { username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, host: process.env.DB_HOSTNAME, dialect: 'mysql', dialectOptions: { ssl: { ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') } } } };
2.7 存储
CLI支持三种存储方式:sequelize
,json
,none
sequelize
: 在sequelize数据库的表中存储迁移和种子json
: 在json文件中存储迁移和种子none
: 不存储任何迁移、种子
迁移存储
默认情况下,CLI会在数据库中创建一个名为SequelizeMeta
的表,其中包含每个已执行迁移的条目。要更改此行为,可以将三个选项添加到配置文件中。使用,您可以选择。 如果选择json
,则可以使用migrationStoragePath
指定文件的路径,或者CLI将写入文件sequelize-meta.json
。 如果要使用sequelize
将信息保留在数据库中,但希望使用其他表,则可以使用migrationStorageTableName
更改表名。
migrationStorage
- 用于指定要用于存储迁移的类型,默认为:sequelize
,如果使用json
则需要指定migrationStoragePath
json
- 使用json
做为迁移存储时,指定JSON文件的路径。默认为:sequelize-meta.json
migrationStorageTableName
- 使用sequelize
做为迁移存储时,用于指定存储表名。默认为:migrationStorageTableName
{ "development": { "username": "root", "password": null, "database": "database_development", "host": "127.0.0.1", "dialect": "mysql", // 指定存储类型,默认: sequelize "migrationStorage": "json", // 指定存储文件名,默认: sequelize-meta.json "migrationStoragePath": "sequelizeMeta.json", // 指定存储表名,默认: SequelizeMeta "migrationStorageTableName": "sequelize_meta" } }
备注:不建议使用none
用作迁移存储。如果决定使用,应该充分考虑未记录迁移执行或未运行的影响。
种子存储
默认情况下,CLI不存储种子的执行记录。如果需要存储,可以使用seederStorage
选项来配置,种子存储的各设置选项与迁移存储类似。当使用json
存储时,可以使用seederStoragePath
来指定路径,或者CLI使用默认的sequelize-data.json
;如果需要存储在数据库中,则可以使用sequelize
选项,这时可以通过seederStorageTableName
来指定表名,默认将使用SequelizeData
。
{ "development": { "username": "root", "password": null, "database": "database_development", "host": "127.0.0.1", "dialect": "mysql", // Use a different storage. Default: none "seederStorage": "json", // Use a different file name. Default: sequelize-data.json "seederStoragePath": "sequelizeData.json", // Use a different table name. Default: SequelizeData "seederStorageTableName": "sequelize_data" } }
2.8 配置连接字符串
可以使用--config
选项来替代数据库配置文件,可以使用--url
选项传入连接字符串。例如:
$ node_modules/.bin/sequelize db:migrate --url 'mysql://root:password@mysql_host.com/database_name'
2.9 使用SSL
连接
使用SSL
连接时,应确保在dialectOptions
和基本配置中都指定了ssl
。
{ "production": { "dialect":"postgres", "ssl": true, "dialectOptions": { "ssl": true } } }
2.10 程序化使用
Sequelize有一个姐妹库:umzug,可以用编程方式处理迁移任务的执行和记录。
3. queryInterface
对象及功能
QueryInterface
是Sequelize
的内置类之一,可以通过Sequelize
对象(Sequelize
实例)的getQueryInterface()
方法获取该类的实例(即:queryInterface
对象):
var queryInterface = sequelize.getQueryInterface();
通过queryInterface
对象中的方法可以对数据进行修改,其中一些方法可以修改数据库的schema
,以下是该类中的部分方法说明:
createTable(tableName, attributes, options)
通过传入的表名tableName
、属性attributes
及其它选项options
创建新表。
createTable()
方法用于在数据库中创建新表。创建表时可以通过attributes
属性定义一些简单或复杂的属性;而options
参数,可以用于定义表所使用的编码及引擎等。
queryInterface.createTable( 'nameOfTheNewTable', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, createdAt: { type: Sequelize.DATE }, updatedAt: { type: Sequelize.DATE }, attr1: Sequelize.STRING, attr2: Sequelize.INTEGER, attr3: { type: Sequelize.BOOLEAN, defaultValue: false, allowNull: false }, //foreign key usage attr4: { type: Sequelize.INTEGER, references: { model: 'another_table_name', key: 'id' }, onUpdate: 'cascade', onDelete: 'cascade' } }, { engine: 'MYISAM', // default: 'InnoDB' charset: 'latin1' // default: null } )
dropTable(tableName, options)
删除已存在的表
queryInterface.dropTable('nameOfTheExistingTable')
dropAllTables(options)
删除数据库中所有已存在的表
queryInterface.dropAllTables()
renameTable(before, after, options)
重命名已存在的表
queryInterface.renameTable('Person', 'User')showAllTables(options)
返回数据库中所有已存在的表的表名
queryInterface.showAllTables().then(function(tableNames) {})
describeTable(tableName, options)
返回表描述,即:返回的一个包含所有属性信息的哈希表
queryInterface.describeTable('Person').then(function(attributes) { /* attributes 结构类似如下: { name: { type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg! allowNull: true, defaultValue: null }, isBetaMember: { type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg! allowNull: false, defaultValue: false } } */ })
addColumn(tableName, attributeName, dataTypeOrOptions, options)
向已存在的表中添加新列,数据类型可以简单或详细定义
queryInterface.addColumn( 'nameOfAnExistingTable', 'nameOfTheNewAttribute', Sequelize.STRING ) // or queryInterface.addColumn( 'nameOfAnExistingTable', 'nameOfTheNewAttribute', { type: Sequelize.STRING, allowNull: false } ) // or with an explicit schema: queryInterface.addColumn({ tableName: 'Person', schema: 'public' }, 'signature', Sequelize.STRING )
removeColumn(tableName, attributeName, options)
移除已存在的表中指定的列
queryInterface.removeColumn('Person', 'signature') // or with an explicit schema: queryInterface.removeColumn({ tableName: 'Person', schema: 'public' }, 'signature');
changeColumn(tableName, attributeName, dataTypeOrOptions, options)
修改属性的元数据。可以修改默认值、是否允许空或数据类型,修改时应该确保完全描述了新的数据类型。
queryInterface.changeColumn( 'nameOfAnExistingTable', 'nameOfAnExistingAttribute', { type: Sequelize.FLOAT, allowNull: false, defaultValue: 0.0 } )
renameColumn(tableName, attrNameBefore, attrNameAfter, options)
重命令属性(修改列名)
queryInterface.renameColumn('Person', 'signature', 'sig')
addIndex(tableName, attributes, options)
通过指定的属性向表中索引。当没有传入options
时,会自动创建索引名。
// This example will create the index person_firstname_lastname queryInterface.addIndex('Person', ['firstname', 'lastname']) // This example will create a unique index with the name SuperDuperIndex using the optional 'options' field. // Possible options: // - indicesType: UNIQUE|FULLTEXT|SPATIAL // - indexName: The name of the index. Default is __ // - parser: For FULLTEXT columns set your parser // - indexType: Set a type for the index, e.g. BTREE. See the documentation of the used dialect // - logging: A function that receives the sql query, e.g. console.log // - where: A hash of attributes to limit your index(Filtered Indexes - MSSQL & PostgreSQL only) queryInterface.addIndex( 'Person', ['firstname', 'lastname'], { indexName: 'SuperDuperIndex', indicesType: 'UNIQUE' } ) queryInterface.addIndex( 'Person', ['firstname', 'lastname'], { where: { lastname: { $ne: null } } } )
removeIndex(tableName, indexNameOrAttributes, options)
移除表中已存在的索引
queryInterface.removeIndex('Person', 'SuperDuperIndex') // or queryInterface.removeIndex('Person', ['firstname', 'lastname'])
addConstraint(tableName, attributes, options)
V4.0.0+
添加一个新约束。
- tableName - 要添加约束的表的表名
- attributes - 应用约束的列名数组
- options - 一个包含约束定义的对象,如:约束名、类型等
options
中可用的选项有:
- type - 约束类型。可用的约束类型有:
- UNIQUE
- DEFAULT (MSSQL only)
- CHECK (MySQL - Ignored by the database engine )
- FOREIGN KEY
- PRIMARY KEY
- name - 约束名。如果不指定,sequelize将根据约束类型、表名、列名自动命名
- defaultValue - 约束默认值
- where - 约束检查条件
- references - Object。指定目标表、列名以创建外键约束
- references.table - 目标表名或表
- references.field - 目录列名 Available constraints:
//UNIQUE queryInterface.addConstraint('Users', ['email'], { type: 'unique', name: 'custom_unique_constraint_name' }); //CHECK queryInterface.addConstraint('Users', ['roles'], { type: 'check', where: { roles: ['user', 'admin', 'moderator', 'guest'] } }); //Default - MSSQL only queryInterface.addConstraint('Users', ['roles'], { type: 'default', defaultValue: 'guest' }); //Primary Key queryInterface.addConstraint('Users', ['username'], { type: 'primary key', name: 'custom_primary_constraint_name' }); //Foreign Key queryInterface.addConstraint('Posts', ['username'], { type: 'FOREIGN KEY', references: { //Required field table: 'target_table_name', field: 'target_column_name' }, onDelete: 'cascade', onUpdate: 'cascade' });
removeConstraint(tableName, constraintName, options)
V4.0.0+
移除表中已经存在的约束
queryInterface.removeConstraint('Users', 'my_constraint_name');
showConstraint(tableName, options)
返回表中已经存在的约束列表
V4.0.0+
queryInterface.showConstraint('Users'); // Returns array of objects/constraints
变更记录
- [2017-06-10] 基于
v4.1.0
首次发布 - [2019-01-21] 基于
v4.42.0
更新