Sequelize — это ORM на основе промисов для Node.js версии 4 и выше. Он поддерживает диалекты PostgreSQL, MySQL, SQLite и MSSQL и обладает надёжной поддержкой транзакций, отношений, репликации чтения и многим другим.
Пример использования
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'|'sqlite'|'postgres'|'mssql',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
// SQLite only
storage: 'path/to/database.sqlite',
// http://docs.sequelizejs.com/manual/tutorial/querying.html#operators
operatorsAliases: false
});
const User = sequelize.define('user', {
username: Sequelize.STRING,
birthday: Sequelize.DATE
});
sequelize.sync()
.then(() => User.create({
username: 'janedoe',
birthday: new Date(1980, 6, 20)
}))
.then(jane => {
console.log(jane.toJSON());
});
Для получения дополнительной информации используйте Начало работы. Если вы хотите узнать больше о Sequelize API, используйте Справочник API
Начало работы
Начало работы
Установка
Sequelize доступен через NPM и Yarn.
// Using NPM
$ npm install --save sequelize
# And one of the following:
$ npm install --save pg pg-hstore
$ npm install --save mysql2
$ npm install --save sqlite3
$ npm install --save tedious // MSSQL
// Using Yarn
$ yarn add sequelize
# And one of the following:
$ yarn add pg pg-hstore
$ yarn add mysql2
$ yarn add sqlite3
$ yarn add tedious // MSSQL
Настройка подключения
Sequelize создаёт пул подключений при инициализации, поэтому желательно создавать только один экземпляр на базу данных, если вы подключаетесь к базе из одного процесса. Если вы подключаетесь к базе из нескольких процессов, вам необходимо создать по одному экземпляру на каждый процесс, но размер пула подключений для каждого экземпляра должен быть равен "максимальному размеру пула подключений, делённому на количество экземпляров". Таким образом, если вы хотите максимальный размер пула подключений 90, а у вас 3 рабочих процесса, размер пула подключений для каждого процесса должен быть 30.
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'|'sqlite'|'postgres'|'mssql',
operatorsAliases: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
// SQLite only
storage: 'path/to/database.sqlite'
});
// Or you can simply use a connection uri
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname');
Конструктор Sequelize принимает множество опций, доступных в Справочнике API.
Проверка подключения
Вы можете использовать функцию .authenticate() для проверки подключения следующим образом.
sequelize
.authenticate()
.then(() => {
console.log('Connection has been established successfully.');
})
.catch(err => {
console.error('Unable to connect to the database:', err);
});
Ваша первая модель
Модели определяются с помощью sequelize.define('name', {attributes}, {options}).
const User = sequelize.define('user', {
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
}
});
// force: true will drop the table if it already exists
User.sync({force: true}).then(() => {
// Table created
return User.create({
firstName: 'John',
lastName: 'Hancock'
});
});
Дополнительную информацию о создании моделей можно найти в Справочнике API моделей
Ваш первый запрос
User.findAll().then(users => {
console.log(users)
})
Дополнительную информацию о функциях поиска моделей, таких как .findAll(), можно найти в разделе Получение данных, а о выполнении специфических запросов, таких как WHERE и JSONB, в разделе Запросы.
Опции модели для всего приложения
Конструктор Sequelize принимает опцию define, которая будет использоваться в качестве опций по умолчанию для всех определённых моделей.
const sequelize = new Sequelize('connectionUri', {
define: {
timestamps: false // true by default
}
});
const User = sequelize.define('user', {}); // timestamps is false by default
const Post = sequelize.define('post', {}, {
timestamps: true // timestamps will now be true
});
Промисы
Sequelize использует промисы Bluebird для управления асинхронным потоком.
Примечание: Sequelize использует независимую копию экземпляра Bluebird. Вы можете получить к нему доступ с помощью Sequelize.Promise , если хотите установить какие-либо специфические для Bluebird опции
Если вы не знакомы с принципом работы промисов, вы можете ознакомиться с ними здесь.
В сущности, промис представляет собой значение, которое будет присутствовать в какой-то момент — «Я обещаю, что в какой-то момент я предоставлю вам результат или ошибку». Это означает, что
// DON'T DO THIS
user = User.findOne()
console.log(user.get('firstName'));
не сработает! Это связано с тем, что user — это объект промиса, а не строка данных из БД. Правильный способ сделать это:
User.findOne().then(user => {
console.log(user.get('firstName'));
});
Если ваша среда или транспилятор поддерживают async/await, это будет работать, но только в теле функции async:
user = await User.findOne()
console.log(user.get('firstName'));
После того, как вы поймёте, что такое промисы и как они работают, используйте справочник по API Bluebird в качестве основного инструмента. В частности, вам, вероятно, часто придётся использовать .all.
Базовое использование
Базовое использование
Чтобы начать работу, сначала нужно создать экземпляр Sequelize. Используйте его следующим образом:
const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'mysql'
});
Это сохранит переданные данные подключения к базе данных и предоставит все дополнительные методы.
Кроме того, вы можете указать хост/порт, отличные от значения по умолчанию:
const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'mysql',
host: "my.server.tld",
port: 9821,
})
Если у вас нет пароля:
const sequelize = new Sequelize({
database: 'db_name',
username: 'username',
password: null,
dialect: 'mysql'
});
Вы также можете использовать строку подключения:
const sequelize = new Sequelize('mysql://user:pass@example.com:9821/db_name', {
// Look to the next section for possible options
})
Опции
Помимо хоста и порта, Sequelize предоставляет множество опций. Вот они:
- См. Sequelize API
- См. Определение модели
- См. Транзакции
const sequelize = new Sequelize('database', 'username', 'password', {
// the sql dialect of the database
// currently supported: 'mysql', 'sqlite', 'postgres', 'mssql'
dialect: 'mysql',
// custom host; default: localhost
host: 'my.server.tld',
// custom port; default: dialect default
port: 12345,
// custom protocol; default: 'tcp'
// postgres only, useful for Heroku
protocol: null,
// disable logging; default: console.log
logging: false,
// you can also pass any dialect options to the underlying dialect library
// - default is empty
// - currently supported: 'mysql', 'postgres', 'mssql'
dialectOptions: {
socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock',
supportBigNumbers: true,
bigNumberStrings: true
},
// the storage engine for sqlite
// - default ':memory:'
storage: 'path/to/database.sqlite',
// disable inserting undefined values as NULL
// - default: false
omitNull: true,
// a flag for using a native library or not.
// in the case of 'pg' -- set this to true will allow SSL support
// - default: false
native: true,
// Specify options, which are used when sequelize.define is called.
// The following example:
// define: { timestamps: false }
// is basically the same as:
// sequelize.define(name, attributes, { timestamps: false })
// so defining the timestamps for each model will be not necessary
define: {
underscored: false
freezeTableName: false,
charset: 'utf8',
dialectOptions: {
collate: 'utf8_general_ci'
},
timestamps: true
},
// similar for sync: you can define this to always force sync for models
sync: { force: true },
// pool configuration used to pool database connections
pool: {
max: 5,
idle: 30000,
acquire: 60000,
},
// isolation level of each transaction
// defaults to dialect default
isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ
})
Подсказка: Вы также можете определить пользовательскую функцию для ведения журнала. Просто передайте функцию. Первым параметром будет строка, которая будет записана в журнал.
Репликация чтения
Sequelize поддерживает репликацию чтения, т. е. использование нескольких серверов, к которым можно подключаться при выполнении запросов SELECT. При репликации чтения вы указываете один или несколько серверов в качестве читающих реплик и один сервер в качестве главного сервера для записи, который обрабатывает все записи и обновления и распространяет их на реплики (обратите внимание, что сам процесс репликации не обрабатывается Sequelize, а должен быть настроен на стороне базы данных).
const sequelize = new Sequelize('database', null, null, {
dialect: 'mysql',
port: 3306
replication: {
read: [
{ host: '8.8.8.8', username: 'read-username', password: 'some-password' },
{ host: '9.9.9.9', username: 'another-username', password: null }
],
write: { host: '1.1.1.1', username: 'write-username', password: 'any-password' }
},
pool: { // If you want to override the options used for the read/write pool you can do so here
max: 20,
idle: 30000
},
})
Если у вас есть общие настройки, которые применяются ко всем репликам, вам не нужно указывать их для каждого экземпляра. В приведенном выше коде имя базы данных и порт передаются во все реплики. То же самое произойдёт с пользователем и паролем, если вы не укажете их для какой-либо реплики. У каждой реплики есть следующие опции: host, port, username, password, database.
Sequelize использует пул для управления подключениями к репликам. Внутренне Sequelize будет поддерживать два пула, созданные с помощью pool конфигурации.
Если вы хотите изменить эти настройки, вы можете передать pool в качестве опции при создании экземпляра Sequelize, как показано выше.
Каждый write или useMaster: true запрос будет использовать пул для записи. Для SELECT будет использоваться пул для чтения. Читающие реплики переключаются с помощью простого планирования по круговому циклу.
Диалекты
С выпуском Sequelize 1.6.0, библиотека стала независимой от конкретных диалектов. Это означает, что вам необходимо самостоятельно добавить соответствующую библиотеку-драйвер в свой проект.
MySQL
Для корректной работы Sequelize с MySQL необходимо установитьmysql2@^1.0.0-rc.10 или выше. После этого можно использовать его следующим образом:
const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'mysql'
})
Примечание: Вы можете передать опции непосредственно в библиотеку диалекта, установив параметр dialectOptions. См. Опции для примеров (в настоящее время поддерживается только mysql).
SQLite
Для совместимости с SQLite необходимо установитьsqlite3@~3.0.0. Настройте Sequelize следующим образом:
const sequelize = new Sequelize('database', 'username', 'password', {
// sqlite! now!
dialect: 'sqlite',
// the storage engine for sqlite
// - default ':memory:'
storage: 'path/to/database.sqlite'
})
Или вы можете использовать строку подключения с путём:
const sequelize = new Sequelize('sqlite:/home/abs/path/dbname.db')
const sequelize = new Sequelize('sqlite:relativePath/dbname.db')
PostgreSQL
Библиотека для PostgreSQL — pg@^5.0.0 || ^6.0.0. Вам просто нужно определить диалект:
const sequelize = new Sequelize('database', 'username', 'password', {
// gimme postgres, please!
dialect: 'postgres'
})
MSSQL
Библиотека для MSSQL — tedious@^1.7.0. Вам просто нужно определить диалект:
const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'mssql'
})
Выполнение произвольных SQL-запросов
Поскольку часто возникают ситуации, когда проще выполнить уже подготовленные SQL-запросы, вы можете использовать функцию sequelize.query.
- См. Sequelize.query API
- См. Типы запросов
Вот как это работает:
// Arguments for raw queries
sequelize.query('your query', [, options])
// Quick example
sequelize.query("SELECT * FROM myTable").then(myTableRows => {
console.log(myTableRows)
})
// If you want to return sequelize instances use the model options.
// This allows you to easily map a query to a predefined model for sequelize e.g:
sequelize
.query('SELECT * FROM projects', { model: Projects })
.then(projects => {
// Each record will now be mapped to the project's model.
console.log(projects)
})
// Options is an object with the following keys:
sequelize
.query('SELECT 1', {
// A function (or false) for logging your queries
// Will get called for every SQL query that gets send
// to the server.
logging: console.log,
// If plain is true, then sequelize will only return the first
// record of the result set. In case of false it will all records.
plain: false,
// Set this to true if you don't have a model definition for your query.
raw: false,
// The type of query you are executing. The query type affects how results are formatted before they are passed back.
type: Sequelize.QueryTypes.SELECT
})
// Note the second argument being null!
// Even if we declared a callee here, the raw: true would
// supersede and return a raw object.
sequelize
.query('SELECT * FROM projects', { raw: true })
.then(projects => {
console.log(projects)
})
Замены в запросе можно выполнить двумя способами: с использованием именованных параметров (начинающихся с : ) или без именования, представленных знаком ?.
Используемый синтаксис зависит от опции replacements, переданной в функцию:
- Если передаётся массив,
?будут заменены в порядке их появления в массиве - Если передаётся объект,
:keyбудут заменены ключами из этого объекта. Если объект содержит ключи, которых нет в запросе, или наоборот, будет выброшено исключение.
sequelize
.query(
'SELECT * FROM projects WHERE status = ?',
{ raw: true, replacements: ['active']
)
.then(projects => {
console.log(projects)
})
sequelize
.query(
'SELECT * FROM projects WHERE status = :status ',
{ raw: true, replacements: { status: 'active' } }
)
.then(projects => {
console.log(projects)
})
Примечание: Если имена атрибутов таблицы содержат точки, результирующие объекты будут вложенными:
sequelize.query('select 1 as `foo.bar.baz`').then(rows => {
console.log(JSON.stringify(rows))
/*
[{
"foo": {
"bar": {
"baz": 1
}
}
}]
*/
})
Определение модели
Определение модели
Для определения соответствий между моделью и таблицей используйте метод define.
const Project = sequelize.define('project', {
title: Sequelize.STRING,
description: Sequelize.TEXT
})
const Task = sequelize.define('task', {
title: Sequelize.STRING,
description: Sequelize.TEXT,
deadline: Sequelize.DATE
})
Вы также можете задать некоторые параметры для каждого столбца:
const Foo = sequelize.define('foo', {
// instantiating will automatically set the flag to true if not set
flag: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true },
// default values for dates => current time
myDate: { type: Sequelize.DATE, defaultValue: Sequelize.NOW },
// setting allowNull to false will add NOT NULL to the column, which means an error will be
// thrown from the DB when the query is executed if the column is null. If you want to check that a value
// is not null before querying the DB, look at the validations section below.
title: { type: Sequelize.STRING, allowNull: false },
// Creating two objects with the same value will throw an error. The unique property can be either a
// boolean, or a string. If you provide the same string for multiple columns, they will form a
// composite unique key.
uniqueOne: { type: Sequelize.STRING, unique: 'compositeIndex' },
uniqueTwo: { type: Sequelize.INTEGER, unique: 'compositeIndex' },
// The unique property is simply a shorthand to create a unique constraint.
someUnique: { type: Sequelize.STRING, unique: true },
// It's exactly the same as creating the index in the model's options.
{ someUnique: { type: Sequelize.STRING } },
{ indexes: [ { unique: true, fields: [ 'someUnique' ] } ] },
// Go on reading for further information about primary keys
identifier: { type: Sequelize.STRING, primaryKey: true },
// autoIncrement can be used to create auto_incrementing integer columns
incrementMe: { type: Sequelize.INTEGER, autoIncrement: true },
// You can specify a custom field name via the 'field' attribute:
fieldWithUnderscores: { type: Sequelize.STRING, field: 'field_with_underscores' },
// It is possible to create foreign keys:
bar_id: {
type: Sequelize.INTEGER,
references: {
// This is a reference to another model
model: Bar,
// This is the column name of the referenced model
key: 'id',
// This declares when to check the foreign key constraint. PostgreSQL only.
deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE
}
}
})
Параметр комментария также может использоваться для таблицы, см. конфигурацию модели.
Отметки о времени
По умолчанию Sequelize добавит атрибуты createdAt и updatedAt в вашу модель, чтобы вы могли узнать, когда запись в базе данных была добавлена и когда она была обновлена в последний раз.
Обратите внимание, что если вы используете миграции Sequelize, вам необходимо добавить поля createdAt и updatedAt в определение миграции:
module.exports = {
up(queryInterface, Sequelize) {
return queryInterface.createTable('my-table', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
// Timestamps
createdAt: Sequelize.DATE,
updatedAt: Sequelize.DATE,
})
},
down(queryInterface, Sequelize) {
return queryInterface.dropTable('my-table');
},
}
Если вы не хотите отметки о времени в своих моделях, хотите только некоторые отметки о времени или работаете с существующей базой данных, где столбцы имеют другие имена, перейдите непосредственно к конфигурации, чтобы узнать, как это сделать.
Типы данных
Ниже приведены некоторые типы данных, поддерживаемые Sequelize. Полный и обновленный список см. в DataTypes.
Sequelize.STRING // VARCHAR(255)
Sequelize.STRING(1234) // VARCHAR(1234)
Sequelize.STRING.BINARY // VARCHAR BINARY
Sequelize.TEXT // TEXT
Sequelize.TEXT('tiny') // TINYTEXT
Sequelize.INTEGER // INTEGER
Sequelize.BIGINT // BIGINT
Sequelize.BIGINT(11) // BIGINT(11)
Sequelize.FLOAT // FLOAT
Sequelize.FLOAT(11) // FLOAT(11)
Sequelize.FLOAT(11, 12) // FLOAT(11,12)
Sequelize.REAL // REAL PostgreSQL only.
Sequelize.REAL(11) // REAL(11) PostgreSQL only.
Sequelize.REAL(11, 12) // REAL(11,12) PostgreSQL only.
Sequelize.DOUBLE // DOUBLE
Sequelize.DOUBLE(11) // DOUBLE(11)
Sequelize.DOUBLE(11, 12) // DOUBLE(11,12)
Sequelize.DECIMAL // DECIMAL
Sequelize.DECIMAL(10, 2) // DECIMAL(10,2)
Sequelize.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
Sequelize.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
Sequelize.DATEONLY // DATE without time.
Sequelize.BOOLEAN // TINYINT(1)
Sequelize.ENUM('value 1', 'value 2') // An ENUM with allowed values 'value 1' and 'value 2'
Sequelize.ARRAY(Sequelize.TEXT) // Defines an array. PostgreSQL only.
Sequelize.ARRAY(Sequelize.ENUM) // Defines an array of ENUM. PostgreSQL only.
Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only.
Sequelize.JSONB // JSONB column. PostgreSQL only.
Sequelize.BLOB // BLOB (bytea for PostgreSQL)
Sequelize.BLOB('tiny') // TINYBLOB (bytea for PostgreSQL. Other options are medium and long)
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)
Sequelize.CIDR // CIDR datatype for PostgreSQL
Sequelize.INET // INET datatype for PostgreSQL
Sequelize.MACADDR // MACADDR datatype for PostgreSQL
Sequelize.RANGE(Sequelize.INTEGER) // Defines int4range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.BIGINT) // Defined int8range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATE) // Defines tstzrange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATEONLY) // Defines daterange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DECIMAL) // Defines numrange range. PostgreSQL only.
Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // Defines array of tstzrange ranges. PostgreSQL only.
Sequelize.GEOMETRY // Spatial column. PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT') // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT', 4326) // Spatial column with geometry type and SRID. PostgreSQL (with PostGIS) or MySQL only.
Тип данных BLOB позволяет вставлять данные как строки, так и как буферы. При выполнении поиска или поиска всех записей в модели со столбцом BLOB эти данные всегда будут возвращаться как буфер.
Если вы работаете с PostgreSQL TIMESTAMP WITHOUT TIME ZONE и вам нужно преобразовать его в другое часовое поясом, используйте собственный анализатор библиотеки pg:
require('pg').types.setTypeParser(1114, stringValue => {
return new Date(stringValue + '+0000');
// e.g., UTC offset. Use any offset that you would like.
});
В дополнение к вышеупомянутому типу, целые числа, большие целые числа, числа с плавающей запятой и двойные также поддерживают свойства unsigned и zerofill, которые могут быть объединены в любом порядке. Имейте в виду, что это не относится к PostgreSQL!
Sequelize.INTEGER.UNSIGNED // INTEGER UNSIGNED
Sequelize.INTEGER(11).UNSIGNED // INTEGER(11) UNSIGNED
Sequelize.INTEGER(11).ZEROFILL // INTEGER(11) ZEROFILL
Sequelize.INTEGER(11).ZEROFILL.UNSIGNED // INTEGER(11) UNSIGNED ZEROFILL
Sequelize.INTEGER(11).UNSIGNED.ZEROFILL // INTEGER(11) UNSIGNED ZEROFILL
В приведенных выше примерах показаны только целые числа, но то же самое можно сделать с большими целыми числами и числами с плавающей запятой
Использование в формате объектов:
// for enums:
sequelize.define('model', {
states: {
type: Sequelize.ENUM,
values: ['active', 'pending', 'deleted']
}
})
Массив (ENUM)
Он поддерживается только PostgreSQL.
Тип массива (ENUM) требует специального обращения. Всякий раз, когда Sequelize будет взаимодействовать с базой данных, ему нужно будет приводить значения массива к типу ENUM.
Поэтому имя этого перечисления должно соответствовать шаблону enum_<table_name>_<col_name>. Если вы используете sync, правильное имя будет сгенерировано автоматически.
Типы диапазонов
Поскольку типы диапазонов содержат дополнительную информацию о включении/исключении границ, использование кортежа для их представления в JavaScript не очень просто.
При предоставлении диапазонов в качестве значений вы можете выбрать следующие API:
// defaults to '["2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")'
// inclusive lower bound, exclusive upper bound
Timeline.create({ range: [new Date(Date.UTC(2016, 0, 1)), new Date(Date.UTC(2016, 1, 1))] });
// control inclusion
const range = [new Date(Date.UTC(2016, 0, 1)), new Date(Date.UTC(2016, 1, 1))];
range.inclusive = false; // '()'
range.inclusive = [false, true]; // '(]'
range.inclusive = true; // '[]'
range.inclusive = [true, false]; // '[)'
// or as a single expression
const range = [
{ value: new Date(Date.UTC(2016, 0, 1)), inclusive: false },
{ value: new Date(Date.UTC(2016, 1, 1)), inclusive: true },
];
// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]'
// composite form
const range = [
{ value: new Date(Date.UTC(2016, 0, 1)), inclusive: false },
new Date(Date.UTC(2016, 1, 1)),
];
// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")'
Timeline.create({ range });
Однако обратите внимание, что всякий раз, когда вы получаете значение диапазона, вы получите:
// stored value: ("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]
range // [Date, Date]
range.inclusive // [false, true]
Убедитесь, что вы преобразуете его в сериализуемый формат перед сериализацией, так как дополнительные свойства массива не будут сериализованы.
Особые случаи
// empty range:
Timeline.create({ range: [] }); // range = 'empty'
// Unbounded range:
Timeline.create({ range: [null, null] }); // range = '[,)'
// range = '[,"2016-01-01 00:00:00+00:00")'
Timeline.create({ range: [null, new Date(Date.UTC(2016, 0, 1))] });
// Infinite range:
// range = '[-infinity,"2016-01-01 00:00:00+00:00")'
Timeline.create({ range: [-Infinity, new Date(Date.UTC(2016, 0, 1))] });
Отложенные действия
При указании внешнего ключа столбца в PostgreSQL необязательно объявлять отложенный тип. Доступны следующие параметры:
// Defer all foreign key constraint check to the end of a transaction
Sequelize.Deferrable.INITIALLY_DEFERRED
// Immediately check the foreign key constraints
Sequelize.Deferrable.INITIALLY_IMMEDIATE
// Don't defer the checks at all
Sequelize.Deferrable.NOT
Последний параметр является значением по умолчанию в PostgreSQL и не позволит динамически изменять правило в транзакции. Подробнее об этом см. в разделе транзакций.
Геттеры и сеттеры
Можно определить функции-геттеры и сеттеры «свойство-объект» для ваших моделей. Их можно использовать как для «защиты» свойств, сопоставленных с полями базы данных, так и для определения «псевдосвойств».
Геттеры и сеттеры можно определить двумя способами (вы можете смешивать эти два подхода):
- как часть определения одного свойства
- как часть параметров модели
Примечание: Если геттер или сеттер определены в обоих местах, функция, найденная в соответствующем определении свойства, всегда будет иметь приоритет.
Определение как части свойства
const Employee = sequelize.define('employee', {
name: {
type: Sequelize.STRING,
allowNull: false,
get() {
const title = this.getDataValue('title');
// 'this' allows you to access attributes of the instance
return this.getDataValue('name') + ' (' + title + ')';
},
},
title: {
type: Sequelize.STRING,
allowNull: false,
set(val) {
this.setDataValue('title', val.toUpperCase());
}
}
});
Employee
.create({ name: 'John Doe', title: 'senior engineer' })
.then(employee => {
console.log(employee.get('name')); // John Doe (SENIOR ENGINEER)
console.log(employee.get('title')); // SENIOR ENGINEER
})
Определение как части параметров модели
Ниже приведен пример определения геттеров и сеттеров в параметрах модели. Геттер fullName — это пример того, как можно определять псевдосвойства в ваших моделях — атрибуты, которые фактически не являются частью вашей схемы базы данных. Фактически, псевдосвойства можно определить двумя способами: с помощью геттеров модели или с помощью столбца с типом VIRTUAL datatype. Виртуальные типы данных могут иметь проверки, а геттеры для виртуальных атрибутов нет.
Обратите внимание, что ссылки this.firstname и this.lastname в функции-геттере fullName вызовут вызов соответствующих функций-геттеров. Если вы этого не хотите, используйте метод getDataValue() для доступа к исходному значению (см. ниже).
const Foo = sequelize.define('foo', {
firstname: Sequelize.STRING,
lastname: Sequelize.STRING
}, {
getterMethods: {
fullName() {
return this.firstname + ' ' + this.lastname
}
},
setterMethods: {
fullName(value) {
const names = value.split(' ');
this.setDataValue('firstname', names.slice(0, -1).join(' '));
this.setDataValue('lastname', names.slice(-1).join(' '));
},
}
});
Вспомогательные функции для использования внутри геттеров и сеттеров
- получение значения базового свойства — всегда используйте
this.getDataValue()
/* a getter for 'title' property */
get() {
return this.getDataValue('title')
}
- установка значения базового свойства — всегда используйте
this.setDataValue()
/* a setter for 'title' property */
set(title) {
this.setDataValue('title', title.toString().toLowerCase());
}
Примечание: Важно придерживаться использования функций setDataValue() и getDataValue() (вместо прямого доступа к свойству «значения данных») — это защищает ваши пользовательские геттеры и сеттеры от изменений в базовых реализациях модели.
Проверки
Проверки модели позволяют указать проверки формата/содержания/наследования для каждого атрибута модели.
Проверки автоматически выполняются при create, update и save. Вы также можете вызвать validate() для проверки экземпляра вручную.
Проверки реализуются с помощью validator.js.
const ValidateMe = sequelize.define('foo', {
foo: {
type: Sequelize.STRING,
validate: {
is: ["^[a-z]+$",'i'], // will only allow letters
is: /^[a-z]+$/i, // same as the previous example using real RegExp
not: ["[a-z]",'i'], // will not allow letters
isEmail: true, // checks for email format (foo@bar.com)
isUrl: true, // checks for url format (http://foo.com)
isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format
isIPv4: true, // checks for IPv4 (129.89.23.1)
isIPv6: true, // checks for IPv6 format
isAlpha: true, // will only allow letters
isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail
isNumeric: true, // will only allow numbers
isInt: true, // checks for valid integers
isFloat: true, // checks for valid floating point numbers
isDecimal: true, // checks for any numbers
isLowercase: true, // checks for lowercase
isUppercase: true, // checks for uppercase
notNull: true, // won't allow null
isNull: true, // only allows null
notEmpty: true, // don't allow empty strings
equals: 'specific value', // only allow a specific value
contains: 'foo', // force specific substrings
notIn: [['foo', 'bar']], // check the value is not one of these
isIn: [['foo', 'bar']], // check the value is one of these
notContains: 'bar', // don't allow specific substrings
len: [2,10], // only allow values with length between 2 and 10
isUUID: 4, // only allow uuids
isDate: true, // only allow date strings
isAfter: "2011-11-05", // only allow date strings after a specific date
isBefore: "2011-11-05", // only allow date strings before a specific date
max: 23, // only allow values <= 23
min: 23, // only allow values >= 23
isCreditCard: true, // check for valid credit card numbers
// custom validations are also possible:
isEven(value) {
if (parseInt(value) % 2 != 0) {
throw new Error('Only even values are allowed!')
// we also are in the model's context here, so this.otherField
// would get the value of otherField if it existed
}
}
}
}
});
Обратите внимание, что если для встроенных функций проверки требуется передать несколько аргументов, аргументы необходимо передавать в массиве. Но если необходимо передать один массив аргументов, например, массив допустимых строк для isIn, он будет интерпретироваться как несколько строковых аргументов вместо одного массива аргументов. Чтобы обойти это, передайте массив аргументов длиной 1, например, [['one', 'two']], как показано выше.
Чтобы использовать пользовательское сообщение об ошибке вместо сообщения, предоставляемого validator.js, используйте объект вместо простого значения или массива аргументов, например, для валидатора, которому не нужны аргументы, можно задать пользовательское сообщение с
isInt: {
msg: "Must be an integer number of pennies"
}
или если также необходимо передать аргументы, добавьте свойствоargs:
isIn: {
args: [['en', 'zh']],
msg: "Must be English or Chinese"
}
При использовании пользовательских функций проверки сообщение об ошибке будет таким, которое содержит брошенный объект Error.
См. проект validator.js для получения дополнительной информации об встроенных методах проверки.
Подсказка: Вы также можете определить пользовательскую функцию для части логирования. Просто передайте функцию. Первый параметр будет строкой, которая будет записана в журнал.
Проверки и allowNull
Если определенное поле модели разрешено иметь значение null (с allowNull: true) и это значение было установлено в null, проверки этого поля не выполняются. Это означает, что вы можете, например, иметь строковое поле, которое проверяет длину строки как минимум в 5 символов, но которое также разрешает null.
Проверки модели
Также можно определить проверки для проверки модели после проверок, специфичных для поля. С помощью этого вы можете, например, убедиться, что ни одно из latitude и longitude не установлено или оба установлены, и выдавать ошибку, если одно из них установлено, а другое — нет.
Методы проверки модели вызываются с контекстом объекта модели и считаются неудачными, если они выбрасывают ошибку, иначе — успешными. Это то же самое, что и с пользовательскими проверками, специфичными для поля.
Любые сообщения об ошибках, собранные при этом, добавляются в объект результата проверки вместе с ошибками проверки поля, с ключами, именованными по ключу метода проверки, завершившегося неудачей, в объекте параметров validate. Хотя для каждого метода проверки модели может быть только одно сообщение об ошибке в любой момент времени, оно представлено как одна строковая ошибка в массиве, чтобы обеспечить максимальную согласованность с ошибками полей.
Пример:
const Pub = Sequelize.define('pub', {
name: { type: Sequelize.STRING },
address: { type: Sequelize.STRING },
latitude: {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: null,
validate: { min: -90, max: 90 }
},
longitude: {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: null,
validate: { min: -180, max: 180 }
},
}, {
validate: {
bothCoordsOrNone() {
if ((this.latitude === null) !== (this.longitude === null)) {
throw new Error('Require either both latitude and longitude or neither')
}
}
}
})
В этом простом случае объект не проходит проверку, если либо широта, либо долгота указаны, но не оба. Если мы попробуем создать объект с широтой, выходящей за пределы диапазона, и без долготы, raging_bullock_arms.validate() может вернуть
{
'latitude': ['Invalid number: latitude'],
'bothCoordsOrNone': ['Require either both latitude and longitude or neither']
}
Настройка
Вы также можете повлиять на то, как Sequelize обрабатывает имена ваших столбцов:
const Bar = sequelize.define('bar', { /* bla */ }, {
// don't add the timestamp attributes (updatedAt, createdAt)
timestamps: false,
// don't delete database entries but set the newly added attribute deletedAt
// to the current date (when deletion was done). paranoid will only work if
// timestamps are enabled
paranoid: true,
// don't use camelcase for automatically added attributes but underscore style
// so updatedAt will be updated_at
underscored: true,
// disable the modification of table names; By default, sequelize will automatically
// transform all passed model names (first parameter of define) into plural.
// if you don't want that, set the following
freezeTableName: true,
// define the table's name
tableName: 'my_very_custom_table_name',
// Enable optimistic locking. When enabled, sequelize will add a version count attribute
// to the model and throw an OptimisticLockingError error when stale instances are saved.
// Set to true or a string with the attribute name you want to use to enable.
version: true
})
Если вы хотите, чтобы sequelize обрабатывал отметки о времени, но хотите только некоторые из них или хотите, чтобы ваши отметки о времени назывались по-другому, вы можете переопределить каждый столбец индивидуально:
const Foo = sequelize.define('foo', { /* bla */ }, {
// don't forget to enable timestamps!
timestamps: true,
// I don't want createdAt
createdAt: false,
// I want updatedAt to actually be called updateTimestamp
updatedAt: 'updateTimestamp',
// And deletedAt to be called destroyTime (remember to enable paranoid for this to work)
deletedAt: 'destroyTime',
paranoid: true
})
Вы также можете изменить движок базы данных, например, на MyISAM. По умолчанию используется InnoDB.
const Person = sequelize.define('person', { /* attributes */ }, {
engine: 'MYISAM'
})
// or globally
const sequelize = new Sequelize(db, user, pw, {
define: { engine: 'MYISAM' }
})
Наконец, вы можете указать комментарий к таблице в MySQL и PG
const Person = sequelize.define('person', { /* attributes */ }, {
comment: "I'm a table comment!"
})
Импорт
Вы также можете хранить определения модели в одном файле, используя метод import. Возвращаемый объект точно такой же, как определен в функции импортированного файла. Так как v1:5.0 Sequelize импорт кэшируется, у вас не возникнет проблем при повторном вызове импорта файла.
// in your server file - e.g. app.js
const Project = sequelize.import(__dirname + "/path/to/models/project")
// The model definition is done in /path/to/models/project.js
// As you might notice, the DataTypes are the very same as explained above
module.exports = (sequelize, DataTypes) => {
return sequelize.define("project", {
name: DataTypes.STRING,
description: DataTypes.TEXT
})
}
Метод import также может принимать коллбэк в качестве аргумента.
sequelize.import('project', (sequelize, DataTypes) => {
return sequelize.define("project", {
name: DataTypes.STRING,
description: DataTypes.TEXT
})
})
Эта дополнительная возможность полезна, когда, например, Error: Cannot find module возникает, хотя /path/to/models/project кажется правильным. Некоторые фреймворки, такие как Meteor, переопределяют require, и выдают "удивительные" результаты, такие как:
Error: Cannot find module '/home/you/meteorApp/.meteor/local/build/programs/server/app/path/to/models/project.js'
Это решается путем передачи версии Meteor для require. Итак, хотя это, вероятно, неудачно…
const AuthorModel = db.import('./path/to/models/project');
…это должно быть успешно…
const AuthorModel = db.import('project', require('./path/to/models/project'));
Оптимистическая блокировка
Sequelize имеет встроенную поддержку оптимистической блокировки через счетчик версии экземпляра модели. Оптимистическая блокировка отключена по умолчанию и может быть включена путем установки свойства version в значение true в определении конкретной модели или в глобальной конфигурации модели. Подробнее см. в разделе конфигурация модели.
Оптимистическая блокировка позволяет одновременный доступ к записям модели для редактирования и предотвращает конфликты перезаписи данных. Она достигает этого, проверяя, внес ли другой процесс изменения в запись с момента ее чтения, и выбрасывает исключение OptimisticLockError, когда обнаруживается конфликт.
Синхронизация с базой данных
При запуске нового проекта у вас не будет структуры базы данных, и с помощью Sequelize вам это не потребуется. Просто укажите структуру модели, и пусть библиотека сделает остальное. В настоящее время поддерживается создание и удаление таблиц:
// Create the tables:
Project.sync()
Task.sync()
// Force the creation!
Project.sync({force: true}) // this will drop the table first and re-create it afterwards
// drop the tables:
Project.drop()
Task.drop()
// event handling:
Project.[sync|drop]().then(() => {
// ok ... everything is nice!
}).catch(error => {
// oooh, did you enter wrong database credentials?
})
Поскольку синхронизация и удаление всех ваших таблиц могут потребовать много строк кода, вы также можете позволить Sequelize выполнить эту работу за вас:
// Sync all models that aren't already in the database
sequelize.sync()
// Force sync all models
sequelize.sync({force: true})
// Drop all tables
sequelize.drop()
// emit handling:
sequelize.[sync|drop]().then(() => {
// woot woot
}).catch(error => {
// whooops
})
Поскольку .sync({ force: true }) — это деструктивная операция, вы можете использовать опцию match в качестве дополнительной проверки безопасности. Опция match сообщает Sequelize сопоставить регулярное выражение с именем базы данных перед синхронизацией — это проверка безопасности для случаев, когда force: true используется в тестах, но не в рабочей кодовой базе.
// This will run .sync() only if database name ends with '_test'
sequelize.sync({ force: true, match: /_test$/ });
Расширение моделей
Модели Sequelize — это классы ES6. Вы можете очень легко добавить пользовательские методы на уровне экземпляра или класса.
const User = sequelize.define('user', { firstname: Sequelize.STRING });
// Adding a class level method
User.classLevelMethod = function() {
return 'foo';
};
// Adding an instance level method
User.prototype.instanceLevelMethod = function() {
return 'bar';
};
Конечно, вы также можете получить доступ к данным экземпляра и сгенерировать виртуальные геттеры:
const User = sequelize.define('user', { firstname: Sequelize.STRING, lastname: Sequelize.STRING });
User.prototype.getFullname = function() {
return [this.firstname, this.lastname].join(' ');
};
// Example:
User.build({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar'
Индексы
Sequelize поддерживает добавление индексов к определению модели, которые будут созданы во время Model.sync() или sequelize.sync.
sequelize.define('user', {}, {
indexes: [
// Create a unique index on email
{
unique: true,
fields: ['email']
},
// Creates a gin index on data with the jsonb_path_ops operator
{
fields: ['data'],
using: 'gin',
operator: 'jsonb_path_ops'
},
// By default index name will be [table]_[fields]
// Creates a multi column partial index
{
name: 'public_by_author',
fields: ['author', 'status'],
where: {
status: 'public'
}
},
// A BTREE index with a ordered field
{
name: 'title_index',
method: 'BTREE',
fields: ['author', {attribute: 'title', collate: 'en_US', order: 'DESC', length: 5}]
}
]
})
Использование моделей
Использование моделей
Получение данных / Поиск
Методы поиска предназначены для запроса данных из базы данных. Они не возвращают простые объекты, а возвращают экземпляры моделей. Поскольку методы поиска возвращают экземпляры моделей, вы можете вызывать любой член экземпляра модели на результате, как описано в документации для экземпляров.
В этом документе мы рассмотрим, что могут делать методы поиска:
find — Поиск одного конкретного элемента в базе данных
// search for known ids
Project.findById(123).then(project => {
// project will be an instance of Project and stores the content of the table entry
// with id 123. if such an entry is not defined you will get null
})
// search for attributes
Project.findOne({ where: {title: 'aProject'} }).then(project => {
// project will be the first entry of the Projects table with the title 'aProject' || null
})
Project.findOne({
where: {title: 'aProject'},
attributes: ['id', ['name', 'title']]
}).then(project => {
// project will be the first entry of the Projects table with the title 'aProject' || null
// project.title will contain the name of the project
})
findOrCreate — Поиск конкретного элемента или его создание, если он отсутствует
Метод findOrCreate может использоваться для проверки, существует ли определенный элемент в базе данных. В этом случае метод вернёт соответствующий экземпляр. Если элемент ещё не существует, он будет создан.
Предположим, у нас есть пустая база данных с моделью User, которая имеет поля username и job.
User
.findOrCreate({where: {username: 'sdepold'}, defaults: {job: 'Technical Lead JavaScript'}})
.spread((user, created) => {
console.log(user.get({
plain: true
}))
console.log(created)
/*
findOrCreate returns an array containing the object that was found or created and a boolean that will be true if a new object was created and false if not, like so:
[ {
username: 'sdepold',
job: 'Technical Lead JavaScript',
id: 1,
createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET),
updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET)
},
true ]
In the example above, the "spread" on line 39 divides the array into its 2 parts and passes them as arguments to the callback function defined beginning at line 39, which treats them as "user" and "created" in this case. (So "user" will be the object from index 0 of the returned array and "created" will equal "true".)
*/
})
Код создал новый экземпляр. Поэтому, когда у нас уже есть экземпляр...
User.create({ username: 'fnord', job: 'omnomnom' })
.then(() => User.findOrCreate({where: {username: 'fnord'}, defaults: {job: 'something else'}}))
.spread((user, created) => {
console.log(user.get({
plain: true
}))
console.log(created)
/*
In this example, findOrCreate returns an array like this:
[ {
username: 'fnord',
job: 'omnomnom',
id: 2,
createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET),
updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET)
},
false
]
The array returned by findOrCreate gets spread into its 2 parts by the "spread" on line 69, and the parts will be passed as 2 arguments to the callback function beginning on line 69, which will then treat them as "user" and "created" in this case. (So "user" will be the object from index 0 of the returned array and "created" will equal "false".)
*/
})
...существующая запись не будет изменена. Обратите внимание на job второго пользователя и тот факт, что создан был false.
findAndCountAll — Поиск нескольких элементов в базе данных, возвращает данные и общее количество
Это удобный метод, который объединяет findAll и count (см. ниже). Он полезен при работе с запросами, связанными с постраничной навигацией, когда вы хотите получить данные с limit и offset, но также вам нужно знать общее количество записей, соответствующих запросу:
Обработчик успешного выполнения всегда получит объект с двумя свойствами:
-
count— целое число, общее количество записей, соответствующих условиям where и другим фильтрам из-за ассоциаций -
rows— массив объектов, записи, соответствующие условиям where и другим фильтрам из-за ассоциаций, в пределах диапазона limit и offset
Project
.findAndCountAll({
where: {
title: {
[Op.like]: 'foo%'
}
},
offset: 10,
limit: 2
})
.then(result => {
console.log(result.count);
console.log(result.rows);
});
Поддерживает include. Только include, помеченные как required, будут добавлены в часть подсчета:
Предположим, вы хотите найти всех пользователей, у которых есть прикрепленный профиль:
User.findAndCountAll({
include: [
{ model: Profile, required: true}
],
limit: 3
});
Поскольку include для Profile имеет required установленным значением, это приведёт к внутреннему соединению, и будут учтены только пользователи, у которых есть профиль. Если мы удалим required из include, будут учтены и пользователи с профилем, и без него. Добавление where условия в include автоматически делает его обязательным:
User.findAndCountAll({
include: [
{ model: Profile, where: { active: true }}
],
limit: 3
});
Приведённый выше запрос будет подсчитывать только пользователей, у которых активен профиль, потому что required неявно устанавливается в true при добавлении условия where в include.
Объект options, который вы передаёте в findAndCountAll, такой же, как и для findAll (описан ниже).
findAll — Поиск нескольких элементов в базе данных
// find multiple entries
Project.findAll().then(projects => {
// projects will be an array of all Project instances
})
// also possible:
Project.all().then(projects => {
// projects will be an array of all Project instances
})
// search for specific attributes - hash usage
Project.findAll({ where: { name: 'A Project' } }).then(projects => {
// projects will be an array of Project instances with the specified name
})
// search within a specific range
Project.findAll({ where: { id: [1,2,3] } }).then(projects => {
// projects will be an array of Projects having the id 1, 2 or 3
// this is actually doing an IN query
})
Project.findAll({
where: {
id: {
[Op.and]: {a: 5}, // AND (a = 5)
[Op.or]: [{a: 5}, {a: 6}], // (a = 5 OR a = 6)
[Op.gt]: 6, // id > 6
[Op.gte]: 6, // id >= 6
[Op.lt]: 10, // id < 10
[Op.lte]: 10, // id <= 10
[Op.ne]: 20, // id != 20
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only)
[Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator)
[Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator)
[Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator)
[Op.any]: [2,3] // ANY ARRAY[2, 3]::INTEGER (PG only)
},
status: {
[Op.not]: false // status NOT FALSE
}
}
})
Сложная фильтрация / запросы OR / NOT
Возможна сложная фильтрация where с несколькими уровнями вложенных условий AND, OR и NOT. Для этого можно использовать or, and или not Operators:
Project.findOne({
where: {
name: 'a project',
[Op.or]: [
{ id: [1,2,3] },
{ id: { [Op.gt]: 10 } }
]
}
})
Project.findOne({
where: {
name: 'a project',
id: {
[Op.or]: [
[1,2,3],
{ [Op.gt]: 10 }
]
}
}
})
Оба фрагмента кода сгенерируют следующее:
SELECT *
FROM `Projects`
WHERE (
`Projects`.`name` = 'a project'
AND (`Projects`.`id` IN (1,2,3) OR `Projects`.`id` > 10)
)
LIMIT 1;
not пример:
Project.findOne({
where: {
name: 'a project',
[Op.not]: [
{ id: [1,2,3] },
{ array: { [Op.contains]: [3,4,5] } }
]
}
});
Сгенерирует:
SELECT *
FROM `Projects`
WHERE (
`Projects`.`name` = 'a project'
AND NOT (`Projects`.`id` IN (1,2,3) OR `Projects`.`array` @> ARRAY[3,4,5]::INTEGER[])
)
LIMIT 1;
Изменение набора данных с помощью limit, offset, order и group
Для получения более релевантных данных можно использовать limit, offset, order и группировку:
// limit the results of the query
Project.findAll({ limit: 10 })
// step over the first 10 elements
Project.findAll({ offset: 10 })
// step over the first 10 elements, and take 2
Project.findAll({ offset: 10, limit: 2 })
Синтаксис группировки и сортировки одинаковый, поэтому ниже показан только один пример для группировки, а остальное для сортировки. Всё, что вы видите ниже, также можно сделать для группировки.
Project.findAll({order: 'title DESC'})
// yields ORDER BY title DESC
Project.findAll({group: 'name'})
// yields GROUP BY name
Обратите внимание, как в двух примерах выше предоставленная строка вставляется в запрос без изменений, т. е. имена столбцов не экранируются. Когда вы предоставляете строку для сортировки/группировки, это всегда будет так. Если вы хотите экранировать имена столбцов, вы должны предоставить массив аргументов, даже если вы хотите сортировать/группировать только по одному столбцу.
something.findOne({
order: [
// will return `name`
['name'],
// will return `username` DESC
['username', 'DESC'],
// will return max(`age`)
sequelize.fn('max', sequelize.col('age')),
// will return max(`age`) DESC
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// will return otherfunction(`col1`, 12, 'lalala') DESC
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// will return otherfunction(awesomefunction(`col`)) DESC, This nesting is potentially infinite!
[sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC']
]
})
Вкратце, элементы массива order/group могут быть следующими:
- Строка — будет заключена в кавычки
- Массив — первый элемент будет заключён в кавычки, второй будет добавлен без изменений
- Объект —
- Raw будет добавлен без кавычек
- Всё остальное игнорируется, и если raw не установлен, запрос завершится ошибкой
- Sequelize.fn и Sequelize.col возвращают функции и имена столбцов в кавычках
Необработанные запросы
Иногда вам может потребоваться большой набор данных, который вы просто хотите отобразить, не изменяя его. Для каждой выбранной строки Sequelize создаёт экземпляр с функциями для обновления, удаления, получения ассоциаций и т. д. Если у вас тысячи строк, это может занять некоторое время. Если вам нужны только необработанные данные и вы ничего не хотите обновлять, вы можете сделать это, чтобы получить необработанные данные.
// Are you expecting a massive dataset from the DB,
// and don't want to spend the time building DAOs for each entry?
// You can pass an extra query option to get the raw data instead:
Project.findAll({ where: { ... }, raw: true })
count — Подсчет вхождений элементов в базе данных
Также есть метод для подсчета объектов базы данных:
Project.count().then(c => {
console.log("There are " + c + " projects!")
})
Project.count({ where: {'id': {[Op.gt]: 25}} }).then(c => {
console.log("There are " + c + " projects with an id greater than 25.")
})
max — Получение наибольшего значения определенного атрибута в определенной таблице
И вот метод для получения максимального значения атрибута:
/*
Let's assume 3 person objects with an attribute age.
The first one is 10 years old,
the second one is 5 years old,
the third one is 40 years old.
*/
Project.max('age').then(max => {
// this will return 40
})
Project.max('age', { where: { age: { [Op.lt]: 20 } } }).then(max => {
// will be 10
})
min — Получение наименьшего значения определенного атрибута в определенной таблице
И вот метод для получения минимального значения атрибута:
/*
Let's assume 3 person objects with an attribute age.
The first one is 10 years old,
the second one is 5 years old,
the third one is 40 years old.
*/
Project.min('age').then(min => {
// this will return 5
})
Project.min('age', { where: { age: { [Op.gt]: 5 } } }).then(min => {
// will be 10
})
sum — Суммирование значений определенных атрибутов
Для вычисления суммы по определённому столбцу таблицы можно использовать метод sum.
/*
Let's assume 3 person objects with an attribute age.
The first one is 10 years old,
the second one is 5 years old,
the third one is 40 years old.
*/
Project.sum('age').then(sum => {
// this will return 55
})
Project.sum('age', { where: { age: { [Op.gt]: 5 } } }).then(sum => {
// will be 50
})
Загрузка с предварительной загрузкой
При получении данных из базы данных есть большая вероятность, что вы также хотите получить ассоциации с тем же запросом — это называется предварительной загрузкой. Основная идея заключается в использовании атрибута include при вызове find или findAll. Предположим следующую настройку:
const User = sequelize.define('user', { name: Sequelize.STRING })
const Task = sequelize.define('task', { name: Sequelize.STRING })
const Tool = sequelize.define('tool', { name: Sequelize.STRING })
Task.belongsTo(User)
User.hasMany(Task)
User.hasMany(Tool, { as: 'Instruments' })
sequelize.sync().then(() => {
// this is where we continue ...
})
Хорошо. Итак, сначала загрузим все задачи с их связанными пользователями.
Task.findAll({ include: [ User ] }).then(tasks => {
console.log(JSON.stringify(tasks))
/*
[{
"name": "A Task",
"id": 1,
"createdAt": "2013-03-20T20:31:40.000Z",
"updatedAt": "2013-03-20T20:31:40.000Z",
"userId": 1,
"user": {
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z"
}
}]
*/
})
Обратите внимание, что аксессор (свойство User в результирующем экземпляре) является единственным, потому что ассоциация является одним к одному.
Следующее: загрузка данных с ассоциациями многие ко многим!
User.findAll({ include: [ Task ] }).then(users => {
console.log(JSON.stringify(users))
/*
[{
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"tasks": [{
"name": "A Task",
"id": 1,
"createdAt": "2013-03-20T20:31:40.000Z",
"updatedAt": "2013-03-20T20:31:40.000Z",
"userId": 1
}]
}]
*/
})
Обратите внимание, что аксессор (свойство Tasks в результирующем экземпляре) является множественным, потому что ассоциация является многими ко многим.
Если ассоциация имеет псевдоним (используя опцию as), вы должны указать этот псевдоним при включении модели. Обратите внимание, как пользователи Tool имеют псевдоним Instruments выше. Чтобы это сделать правильно, вам нужно указать модель, которую вы хотите загрузить, а также псевдоним:
User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => {
console.log(JSON.stringify(users))
/*
[{
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1
}]
}]
*/
})
Вы также можете включить по имени псевдонима, указав строку, соответствующую имени псевдонима ассоциации:
User.findAll({ include: ['Instruments'] }).then(users => {
console.log(JSON.stringify(users))
/*
[{
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1
}]
}]
*/
})
User.findAll({ include: [{ association: 'Instruments' }] }).then(users => {
console.log(JSON.stringify(users))
/*
[{
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1
}]
}]
*/
})
При предварительной загрузке мы также можем фильтровать связанную модель с помощью where. Это вернет все User , в которых условие where модели Tool соответствует строкам.
User.findAll({
include: [{
model: Tool,
as: 'Instruments',
where: { name: { [Op.like]: '%ooth%' } }
}]
}).then(users => {
console.log(JSON.stringify(users))
/*
[{
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1
}]
}],
[{
"name": "John Smith",
"id": 2,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1
}]
}],
*/
})
Когда загружаемая с предварительной загрузкой модель отфильтрована с помощью include.where, то include.required неявно устанавливается в true. Это означает, что выполняется внутреннее соединение, возвращающее родительские модели с соответствующими дочерними моделями.
Условие where на верхнем уровне с моделями, загруженными с предварительной загрузкой
Чтобы перенести условия where из вложенной модели из условия ON на верхний уровень WHERE, можно использовать синтаксис '$nested.column$':
User.findAll({
where: {
'$Instruments.name$': { [Op.iLike]: '%ooth%' }
},
include: [{
model: Tool,
as: 'Instruments'
}]
}).then(users => {
console.log(JSON.stringify(users));
/*
[{
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1
}]
}],
[{
"name": "John Smith",
"id": 2,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1
}]
}],
*/
Включение всех атрибутов
Для включения всех атрибутов можно передать единственный объект с all: true:
User.findAll({ include: [{ all: true }]});
Включение мягко удаленных записей
В случае, если вы хотите загрузить с предварительной загрузкой мягко удаленные записи, вы можете сделать это, установив include.paranoid в false
User.findAll({
include: [{
model: Tool,
where: { name: { [Op.like]: '%ooth%' } },
paranoid: false // query and loads the soft deleted records
}]
});
Сортировка ассоциаций, загруженных с предварительной загрузкой
В случае связи один ко многим.
Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] });
Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] });
Company.findAll({
include: [ { model: Division, as: 'Div' } ],
order: [ [ { model: Division, as: 'Div' }, 'name' ] ]
});
Company.findAll({
include: [ { model: Division, as: 'Div' } ],
order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ]
});
Company.findAll({
include: [ { model: Division, include: [ Department ] } ],
order: [ [ Division, Department, 'name' ] ]
});
В случае соединений многие ко многим вы также можете сортировать по атрибутам в таблице через.
Company.findAll({
include: [ { model: Division, include: [ Department ] } ],
order: [ [ Division, DepartmentDivision, 'name' ] ]
});
Вложенная предварительная загрузка
Вы можете использовать вложенную предварительную загрузку для загрузки всех связанных моделей связанной модели:
User.findAll({
include: [
{model: Tool, as: 'Instruments', include: [
{model: Teacher, include: [ /* etc */]}
]}
]
}).then(users => {
console.log(JSON.stringify(users))
/*
[{
"name": "John Doe",
"id": 1,
"createdAt": "2013-03-20T20:31:45.000Z",
"updatedAt": "2013-03-20T20:31:45.000Z",
"Instruments": [{ // 1:M and N:M association
"name": "Toothpick",
"id": 1,
"createdAt": null,
"updatedAt": null,
"userId": 1,
"Teacher": { // 1:1 association
"name": "Jimi Hendrix"
}
}]
}]
*/
})
Это даст внешнее соединение. Однако условие where в связанной модели создаст внутреннее соединение и вернёт только экземпляры, у которых есть соответствующие подмодели. Для возврата всех родительских экземпляров следует добавить required: false.
User.findAll({
include: [{
model: Tool,
as: 'Instruments',
include: [{
model: Teacher,
where: {
school: "Woodstock Music School"
},
required: false
}]
}]
}).then(users => {
/* ... */
})
Приведённый выше запрос вернёт всех пользователей и все их инструменты, но только тех учителей, которые связаны с Woodstock Music School.
Включение всех также поддерживает вложенную загрузку:
User.findAll({ include: [{ all: true, nested: true }]});
Запросы
Запросы
Атрибуты
Для выбора только некоторых атрибутов можно использовать опцию attributes. Чаще всего передаётся массив:
Model.findAll({
attributes: ['foo', 'bar']
});
SELECT foo, bar ...
Атрибуты можно переименовать, используя вложенный массив:
Model.findAll({
attributes: ['foo', ['bar', 'baz']]
});
SELECT foo, bar AS baz ...
Для агрегаций можно использовать sequelize.fn:
Model.findAll({
attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']]
});
SELECT COUNT(hats) AS no_hats ...
При использовании функции агрегации необходимо предоставить ей псевдоним, чтобы иметь возможность обратиться к ней из модели. В приведенном выше примере вы можете получить количество шляп с помощью instance.get('no_hats').
Иногда может быть утомительно перечислять все атрибуты модели, если требуется добавить только агрегацию:
// This is a tiresome way of getting the number of hats...
Model.findAll({
attributes: ['id', 'foo', 'bar', 'baz', 'quz', [sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']]
});
// This is shorter, and less error prone because it still works if you add / remove attributes
Model.findAll({
attributes: { include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] }
});
SELECT id, foo, bar, baz, quz, COUNT(hats) AS no_hats ...
Аналогичным образом, также возможно удалить выбранные атрибуты:
Model.findAll({
attributes: { exclude: ['baz'] }
});
SELECT id, foo, bar, quz ...
Where
При запросе с помощью findAll/find или при выполнении массовых обновлений/удалений можно передать объект where, чтобы отфильтровать запрос.
where обычно принимает объект пар атрибут:значение, где значение может быть примитивом для совпадения по равенству или объектом с ключами для других операторов.
Также возможно генерировать сложные условия AND/OR, вкладывая наборы or и and Operators.
Основы
const Op = Sequelize.Op;
Post.findAll({
where: {
authorId: 2
}
});
// SELECT * FROM post WHERE authorId = 2
Post.findAll({
where: {
authorId: 12,
status: 'active'
}
});
// SELECT * FROM post WHERE authorId = 12 AND status = 'active';
Post.findAll({
where: {
[Op.or]: [{authorId: 12}, {authorId: 13}]
}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
Post.findAll({
where: {
authorId: {
[Op.or]: [12, 13]
}
}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
Post.destroy({
where: {
status: 'inactive'
}
});
// DELETE FROM post WHERE status = 'inactive';
Post.update({
updatedAt: null,
}, {
where: {
deletedAt: {
[Op.ne]: null
}
}
});
// UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL;
Post.findAll({
where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6)
});
// SELECT * FROM post WHERE char_length(status) = 6;
Операторы
Sequelize предоставляет символьные операторы, которые можно использовать для создания более сложных сравнений —
const Op = Sequelize.Op
[Op.and]: {a: 5} // AND (a = 5)
[Op.or]: [{a: 5}, {a: 6}] // (a = 5 OR a = 6)
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
[Op.lte]: 10, // <= 10
[Op.ne]: 20, // != 20
[Op.eq]: 3, // = 3
[Op.not]: true, // IS NOT TRUE
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat' // NOT LIKE '%hat'
[Op.iLike]: '%hat' // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat' // NOT ILIKE '%hat' (PG only)
[Op.regexp]: '^[h|a|t]' // REGEXP/~ '^[h|a|t]' (MySQL/PG only)
[Op.notRegexp]: '^[h|a|t]' // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only)
[Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (PG only)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only)
[Op.like]: { [Op.any]: ['cat', 'hat']}
// LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike
[Op.overlap]: [1, 2] // && [1, 2] (PG array overlap operator)
[Op.contains]: [1, 2] // @> [1, 2] (PG array contains operator)
[Op.contained]: [1, 2] // <@ [1, 2] (PG array contained by operator)
[Op.any]: [2,3] // ANY ARRAY[2, 3]::INTEGER (PG only)
[Op.col]: 'user.organization_id' // = "user"."organization_id", with dialect specific column identifiers, PG in this example
Диапазонные операторы
Диапазонные типы можно запросить со всеми поддерживаемыми операторами.
Учитывайте, что предоставленное значение диапазона может определять включение/исключение границ также.
// All the above equality and inequality operators plus the following:
[Op.contains]: 2 // @> '2'::integer (PG range contains element operator)
[Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator)
[Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator)
[Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator)
[Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator)
[Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator)
[Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator)
[Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator)
[Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator)
Комбинации
const Op = Sequelize.Op;
{
rank: {
[Op.or]: {
[Op.lt]: 1000,
[Op.eq]: null
}
}
}
// rank < 1000 OR rank IS NULL
{
createdAt: {
[Op.lt]: new Date(),
[Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000)
}
}
// createdAt < [timestamp] AND createdAt > [timestamp]
{
[Op.or]: [
{
title: {
[Op.like]: 'Boat%'
}
},
{
description: {
[Op.like]: '%boat%'
}
}
]
}
// title LIKE 'Boat%' OR description LIKE '%boat%'
Псевдонимы операторов
Sequelize позволяет задавать определённые строки в качестве псевдонимов для операторов —
const Op = Sequelize.Op;
const operatorsAliases = {
$gt: Op.gt
}
const connection = new Sequelize(db, user, pass, { operatorsAliases })
[Op.gt]: 6 // > 6
$gt: 6 // same as using Op.gt (> 6)
Безопасность операторов
Использование Sequelize без псевдонимов повышает безопасность. Некоторые фреймворки автоматически преобразуют пользовательский ввод в js объекты, и если вы не очистите свой ввод, возможно, вы сможете внедрить объект со строковыми операторами в Sequelize.
Отсутствие строковых псевдонимов для операторов сильно уменьшит вероятность внедрения операторов, но вы всегда должны правильно проверять и очищать пользовательский ввод.
Для обратной совместимости Sequelize по умолчанию устанавливает следующие псевдонимы: $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
В настоящее время также установлены следующие устаревшие псевдонимы, но планируется их полное удаление в ближайшем будущем: ne, not, in, notIn, gte, gt, lte, lt, like, ilike, $ilike, nlike, $notlike, notilike, .., between, !.., notbetween, nbetween, overlap, &&, @>, <@
Для большей безопасности настоятельно рекомендуется использовать Sequelize.Op и не полагаться на псевдонимы строк вообще. Вы можете ограничить псевдонимы, необходимые для вашей приложения, задав опцию operatorsAliases. Помните о необходимости очистки пользовательского ввода, особенно когда вы напрямую передаёте его в методы Sequelize.
const Op = Sequelize.Op;
//use sequelize without any operators aliases
const connection = new Sequelize(db, user, pass, { operatorsAliases: false });
//use sequelize with only alias for $and => Op.and
const connection2 = new Sequelize(db, user, pass, { operatorsAliases: { $and: Op.and } });
Sequelize предупредит вас, если вы используете стандартные псевдонимы и не ограничиваете их. Если вы хотите продолжить использование всех стандартных псевдонимов (исключая устаревшие) без предупреждения, вы можете передать опцию operatorsAliases —
const Op = Sequelize.Op;
const operatorsAliases = {
$eq: Op.eq,
$ne: Op.ne,
$gte: Op.gte,
$gt: Op.gt,
$lte: Op.lte,
$lt: Op.lt,
$not: Op.not,
$in: Op.in,
$notIn: Op.notIn,
$is: Op.is,
$like: Op.like,
$notLike: Op.notLike,
$iLike: Op.iLike,
$notILike: Op.notILike,
$regexp: Op.regexp,
$notRegexp: Op.notRegexp,
$iRegexp: Op.iRegexp,
$notIRegexp: Op.notIRegexp,
$between: Op.between,
$notBetween: Op.notBetween,
$overlap: Op.overlap,
$contains: Op.contains,
$contained: Op.contained,
$adjacent: Op.adjacent,
$strictLeft: Op.strictLeft,
$strictRight: Op.strictRight,
$noExtendRight: Op.noExtendRight,
$noExtendLeft: Op.noExtendLeft,
$and: Op.and,
$or: Op.or,
$any: Op.any,
$all: Op.all,
$values: Op.values,
$col: Op.col
};
const connection = new Sequelize(db, user, pass, { operatorsAliases });
JSON
Тип данных JSON поддерживается только диалектами PostgreSQL, SQLite и MySQL.
PostgreSQL
Тип данных JSON в PostgreSQL хранит значение в виде обычного текста, а не в двоичном представлении. Если вам просто нужно сохранить и извлечь JSON-представление, использование JSON займёт меньше места на диске и меньше времени для построения из входного представления. Однако, если вам нужно выполнить какие-либо операции над значением JSON, предпочтительнее использовать тип данных JSONB, описанный ниже.
MSSQL
MSSQL не имеет типа данных JSON, однако он предоставляет поддержку JSON, хранящегося в виде строк, с помощью определённых функций начиная с SQL Server 2016. Используя эти функции, вы сможете запросить JSON, хранящийся в строке, но все возвращаемые значения необходимо будет разобрать отдельно.
// ISJSON - to test if a string contains valid JSON
User.findAll({
where: sequelize.where(sequelize.fn('ISJSON', sequelize.col('userDetails')), 1)
})
// JSON_VALUE - extract a scalar value from a JSON string
User.findAll({
attributes: [[ sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), 'address line 1']]
})
// JSON_VALUE - query a scalar value from a JSON string
User.findAll({
where: sequelize.where(sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), '14, Foo Street')
})
// JSON_QUERY - extract an object or array
User.findAll({
attributes: [[ sequelize.fn('JSON_QUERY', sequelize.col('userDetails'), '$.address'), 'full address']]
})
JSONB
JSONB можно запросить тремя способами.
Вложенный объект
{
meta: {
video: {
url: {
[Op.ne]: null
}
}
}
}
Вложенный ключ
{
"meta.audio.length": {
[Op.gt]: 20
}
}
Содержательность
{
"meta": {
[Op.contains]: {
site: {
url: 'http://google.com'
}
}
}
}
Связи / Ассоциации
// Find all projects with a least one task where task.state === project.state
Project.findAll({
include: [{
model: Task,
where: { state: Sequelize.col('project.state') }
}]
})
Пагинация / Ограничение
// Fetch 10 instances/rows
Project.findAll({ limit: 10 })
// Skip 8 instances/rows
Project.findAll({ offset: 8 })
// Skip 5 instances and fetch the 5 after that
Project.findAll({ offset: 5, limit: 5 })
Сортировка
order принимает массив элементов для сортировки запроса или метод sequelize. Обычно вы захотите использовать кортеж/массив атрибута, направления или просто направления, чтобы обеспечить правильное экранирование.
Subtask.findAll({
order: [
// Will escape title and validate DESC against a list of valid direction parameters
['title', 'DESC'],
// Will order by max(age)
sequelize.fn('max', sequelize.col('age')),
// Will order by max(age) DESC
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// Will order by otherfunction(`col1`, 12, 'lalala') DESC
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// Will order an associated model's created_at using the model name as the association's name.
[Task, 'createdAt', 'DESC'],
// Will order through an associated model's created_at using the model names as the associations' names.
[Task, Project, 'createdAt', 'DESC'],
// Will order by an associated model's created_at using the name of the association.
['Task', 'createdAt', 'DESC'],
// Will order by a nested associated model's created_at using the names of the associations.
['Task', 'Project', 'createdAt', 'DESC'],
// Will order by an associated model's created_at using an association object. (preferred method)
[Subtask.associations.Task, 'createdAt', 'DESC'],
// Will order by a nested associated model's created_at using association objects. (preferred method)
[Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'],
// Will order by an associated model's created_at using a simple association object.
[{model: Task, as: 'Task'}, 'createdAt', 'DESC'],
// Will order by a nested associated model's created_at simple association objects.
[{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC']
]
// Will order by max age descending
order: sequelize.literal('max(age) DESC')
// Will order by max age ascending assuming ascending is the default order when direction is omitted
order: sequelize.fn('max', sequelize.col('age'))
// Will order by age ascending assuming ascending is the default order when direction is omitted
order: sequelize.col('age')
// Will order randomly based on the dialect (instead of fn('RAND') or fn('RANDOM'))
order: sequelize.random()
})
Подсказка для таблицы
tableHint можно использовать для необязательной передачи подсказки для таблицы при использовании mssql. Подсказка должна быть значением из Sequelize.TableHints и должна использоваться только в крайнем случае. В настоящее время поддерживается только одна подсказка для таблицы на запрос.
Подсказки для таблиц переопределяют стандартное поведение оптимизатора запросов mssql, задавая определенные параметры. Они влияют только на таблицу или представление, упомянутые в данном условии.
const TableHints = Sequelize.TableHints;
Project.findAll({
// adding the table hint NOLOCK
tableHint: TableHints.NOLOCK
// this will generate the SQL 'WITH (NOLOCK)'
})
Экземпляры
Экземпляры
Создание неперсистентного экземпляра
Для создания экземпляров определенных классов, просто выполните следующие действия. Вы, возможно, узнаете синтаксис, если в прошлом программировали на Ruby. Использование метода build вернёт несохранённый объект, который вам явно нужно сохранить.
const project = Project.build({
title: 'my awesome project',
description: 'woot woot. this will make me a rich man'
})
const task = Task.build({
title: 'specify the project idea',
description: 'bla',
deadline: new Date()
})
Созданные экземпляры автоматически получат значения по умолчанию, когда они были определены:
// first define the model
const Task = sequelize.define('task', {
title: Sequelize.STRING,
rating: { type: Sequelize.STRING, defaultValue: 3 }
})
// now instantiate an object
const task = Task.build({title: 'very important task'})
task.title // ==> 'very important task'
task.rating // ==> 3
Чтобы сохранить его в базе данных, используйте метод save и обработайте события ... при необходимости:
project.save().then(() => {
// my nice callback stuff
})
task.save().catch(error => {
// mhhh, wth!
})
// you can also build, save and access the object with chaining:
Task
.build({ title: 'foo', description: 'bar', deadline: new Date() })
.save()
.then(anotherTask => {
// you can now access the currently saved task with the variable anotherTask... nice!
})
.catch(error => {
// Ooops, do some error-handling
})
Создание персистентных экземпляров
В то время как экземпляр, созданный с помощью .build(), требует явного вызова .save() для сохранения в базе данных, .create() опускает это требование и автоматически сохраняет данные вашего экземпляра при вызове.
Task.create({ title: 'foo', description: 'bar', deadline: new Date() }).then(task => {
// you can now access the newly created task via the variable task
})
Также возможно определить, какие атрибуты можно установить через метод create. Это особенно удобно, если вы создаёте записи в базе данных на основе формы, которую может заполнить пользователь. Это, например, позволит ограничить модель User установкой только имени пользователя и адреса, но не флага администратора:
User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] }).then(user => {
// let's assume the default of isAdmin is false:
console.log(user.get({
plain: true
})) // => { username: 'barfooz', isAdmin: false }
})
Обновление/Сохранение/Персистентность экземпляра
Теперь давайте изменим некоторые значения и сохраним изменения в базе данных... Есть два способа сделать это:
// way 1
task.title = 'a very different title now'
task.save().then(() => {})
// way 2
task.update({
title: 'a very different title now'
}).then(() => {})
Также можно определить, какие атрибуты должны быть сохранены при вызове save, передав массив имён столбцов. Это полезно, когда вы устанавливаете атрибуты на основе ранее определённого объекта. Например, если вы получаете значения объекта через форму веб-приложения. Кроме того, это используется внутри update. Вот как это выглядит:
task.title = 'foooo'
task.description = 'baaaaaar'
task.save({fields: ['title']}).then(() => {
// title will now be 'foooo' but description is the very same as before
})
// The equivalent call using update looks like this:
task.update({ title: 'foooo', description: 'baaaaaar'}, {fields: ['title']}).then(() => {
// title will now be 'foooo' but description is the very same as before
})
Если вы вызываете save без изменения каких-либо атрибутов, этот метод ничего не выполнит;
Удаление персистентных экземпляров
После создания объекта и получения ссылки на него, вы можете удалить его из базы данных. Соответствующий метод — destroy.
Task.create({ title: 'a task' }).then(task => {
// now you see me...
return task.destroy();
}).then(() => {
// now i'm gone :)
})
Если параметр paranoid имеет значение true, объект не будет удалён, вместо этого столбец deletedAt будет установлен на текущую метку времени. Чтобы принудительно выполнить удаление, вы можете передать force: true вызову destroy:
task.destroy({ force: true })
Работа со множеством строк (создание, обновление и удаление нескольких строк одновременно)
В дополнение к обновлению одного экземпляра, вы также можете создавать, обновлять и удалять несколько экземпляров одновременно. Функции, которые вам нужны, называются:
Model.bulkCreateModel.updateModel.destroy
Поскольку вы работаете с несколькими моделями, обратные вызовы не вернут экземпляры DAO. BulkCreate вернёт массив экземпляров/DAO моделей, однако, в отличие от create, они не будут содержать результирующих значений атрибутов autoIncrement. update и destroy вернут количество затронутых строк.
Сначала давайте рассмотрим bulkCreate
User.bulkCreate([
{ username: 'barfooz', isAdmin: true },
{ username: 'foo', isAdmin: true },
{ username: 'bar', isAdmin: false }
]).then(() => { // Notice: There are no arguments here, as of right now you'll have to...
return User.findAll();
}).then(users => {
console.log(users) // ... in order to get the array of user objects
})
Для обновления нескольких строк одновременно:
Task.bulkCreate([
{subject: 'programming', status: 'executing'},
{subject: 'reading', status: 'executing'},
{subject: 'programming', status: 'finished'}
]).then(() => {
return Task.update(
{ status: 'inactive' }, /* set attributes' value */
{ where: { subject: 'programming' }} /* where criteria */
);
}).spread((affectedCount, affectedRows) => {
// .update returns two values in an array, therefore we use .spread
// Notice that affectedRows will only be defined in dialects which support returning: true
// affectedCount will be 2
return Task.findAll();
}).then(tasks => {
console.log(tasks) // the 'programming' tasks will both have a status of 'inactive'
})
И для их удаления:
Task.bulkCreate([
{subject: 'programming', status: 'executing'},
{subject: 'reading', status: 'executing'},
{subject: 'programming', status: 'finished'}
]).then(() => {
return Task.destroy({
where: {
subject: 'programming'
},
truncate: true /* this will ignore where and truncate the table instead */
});
}).then(affectedRows => {
// affectedRows will be 2
return Task.findAll();
}).then(tasks => {
console.log(tasks) // no programming, just reading :(
})
Если вы принимаете значения напрямую от пользователя, может быть полезно ограничить столбцы, которые вы хотите фактически вставить. bulkCreate() принимает объект опций в качестве второго параметра. Объект может иметь параметр fields (массив), чтобы указать, какие поля вы хотите явно создать.
User.bulkCreate([
{ username: 'foo' },
{ username: 'bar', admin: true}
], { fields: ['username'] }).then(() => {
// nope bar, you can't be admin!
})
bulkCreate изначально был разработан как основной/быстрый способ вставки записей, но иногда вам нужна возможность вставлять несколько строк одновременно, не жертвуя проверкой модели, даже когда вы явно указываете Sequelize, какие столбцы нужно просматривать. Это можно сделать, добавив свойство validate: true в объект опций.
const Tasks = sequelize.define('task', {
name: {
type: Sequelize.STRING,
validate: {
notNull: { args: true, msg: 'name cannot be null' }
}
},
code: {
type: Sequelize.STRING,
validate: {
len: [3, 10]
}
}
})
Tasks.bulkCreate([
{name: 'foo', code: '123'},
{code: '1234'},
{name: 'bar', code: '1'}
], { validate: true }).catch(errors => {
/* console.log(errors) would look like:
[
{ record:
...
name: 'SequelizeBulkRecordError',
message: 'Validation error',
errors:
{ name: 'SequelizeValidationError',
message: 'Validation error',
errors: [Object] } },
{ record:
...
name: 'SequelizeBulkRecordError',
message: 'Validation error',
errors:
{ name: 'SequelizeValidationError',
message: 'Validation error',
errors: [Object] } }
]
*/
})
Значения экземпляра
Если вы выведете экземпляр на экран, вы заметите, что есть много дополнительной информации. Чтобы скрыть такую информацию и сократить её до интересной информации, вы можете использовать атрибут get. Вызов с опцией plain = true вернёт только значения экземпляра.
Person.create({
name: 'Rambow',
firstname: 'John'
}).then(john => {
console.log(john.get({
plain: true
}))
})
// result:
// { name: 'Rambow',
// firstname: 'John',
// id: 1,
// createdAt: Tue, 01 May 2012 19:12:16 GMT,
// updatedAt: Tue, 01 May 2012 19:12:16 GMT
// }
Подсказка: Вы также можете преобразовать экземпляр в JSON, используя JSON.stringify(instance). Это, по сути, вернёт то же самое, что и values.
Перезагрузка экземпляров
Если вам нужно синхронизировать ваш экземпляр, вы можете использовать метод reload. Он загрузит текущие данные из базы данных и перезапишет атрибуты модели, к которой этот метод был применён.
Person.findOne({ where: { name: 'john' } }).then(person => {
person.name = 'jane'
console.log(person.name) // 'jane'
person.reload().then(() => {
console.log(person.name) // 'john'
})
})
Инкрементирование
Для инкрементирования значений экземпляра без проблем с одновременным доступом, вы можете использовать increment.
Во-первых, вы можете определить поле и значение, которое вы хотите добавить к нему.
User.findById(1).then(user => {
return user.increment('my-integer-field', {by: 2})
}).then(user => {
// Postgres will return the updated user by default (unless disabled by setting { returning: false })
// In other dialects, you'll want to call user.reload() to get the updated instance...
})
Во-вторых, вы можете определить несколько полей и значения, которые вы хотите добавить к ним.
User.findById(1).then(user => {
return user.increment([ 'my-integer-field', 'my-very-other-field' ], {by: 2})
}).then(/* ... */)
В-третьих, вы можете определить объект, содержащий поля и их значения инкремента.
User.findById(1).then(user => {
return user.increment({
'my-integer-field': 2,
'my-very-other-field': 3
})
}).then(/* ... */)
Декрементирование
Для декрементирования значений экземпляра без проблем с одновременным доступом, вы можете использовать decrement.
Во-первых, вы можете определить поле и значение, которое вы хотите добавить к нему.
User.findById(1).then(user => {
return user.decrement('my-integer-field', {by: 2})
}).then(user => {
// Postgres will return the updated user by default (unless disabled by setting { returning: false })
// In other dialects, you'll want to call user.reload() to get the updated instance...
})
Во-вторых, вы можете определить несколько полей и значения, которые вы хотите добавить к ним.
User.findById(1).then(user => {
return user.decrement([ 'my-integer-field', 'my-very-other-field' ], {by: 2})
}).then(/* ... */)
В-третьих, вы можете определить объект, содержащий поля и их значения декремента.
User.findById(1).then(user => {
return user.decrement({
'my-integer-field': 2,
'my-very-other-field': 3
})
}).then(/* ... */)
Ассоциации
Ассоциации
В этом разделе описаны различные типы ассоциаций в sequelize. При вызове метода, такого как User.hasOne(Project), мы говорим, что модель User (модель, на которой вызывается функция) является источником, а модель Project (модель, передаваемая в качестве аргумента) является целью.
Одно-к-одному ассоциации
Одно-к-одному ассоциации — это ассоциации между ровно двумя моделями, соединёнными одним внешним ключом.
BelongsTo
Ассоциации BelongsTo — это ассоциации, где внешний ключ для одно-к-одному отношения существует в источниковой модели.
Простой пример — Игрок, являющийся частью Команды, с внешним ключом в модели игрока.
const Player = this.sequelize.define('player', {/* attributes */});
const Team = this.sequelize.define('team', {/* attributes */});
Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team
Внешние ключи
По умолчанию внешний ключ для отношения belongsTo будет сгенерирован из имени целевой модели и имени первичного ключа целевой модели.
По умолчанию используется регистр camelCase, однако, если исходная модель настроена с underscored: true, внешний ключ будет snake_case.
const User = this.sequelize.define('user', {/* attributes */})
const Company = this.sequelize.define('company', {/* attributes */});
User.belongsTo(Company); // Will add companyId to user
const User = this.sequelize.define('user', {/* attributes */}, {underscored: true})
const Company = this.sequelize.define('company', {
uuid: {
type: Sequelize.UUID,
primaryKey: true
}
});
User.belongsTo(Company); // Will add company_uuid to user
В тех случаях, когда as определено, оно будет использовано вместо имени целевой модели.
const User = this.sequelize.define('user', {/* attributes */})
const UserRole = this.sequelize.define('userRole', {/* attributes */});
User.belongsTo(UserRole, {as: 'role'}); // Adds roleId to user rather than userRoleId
Во всех случаях внешний ключ по умолчанию может быть переопределён с помощью параметра foreignKey. При использовании опции внешнего ключа Sequelize будет использовать её значение напрямую:
const User = this.sequelize.define('user', {/* attributes */})
const Company = this.sequelize.define('company', {/* attributes */});
User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User
Ключи цели
Ключ цели — это столбец в целевой модели, к которому ссылается внешний ключ в столбце источника. По умолчанию ключ цели для отношения belongsTo будет первичным ключом целевой модели. Чтобы определить пользовательский столбец, используйте опцию targetKey.
const User = this.sequelize.define('user', {/* attributes */})
const Company = this.sequelize.define('company', {/* attributes */});
User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // Adds fk_companyname to User
HasOne
Ассоциации HasOne — это ассоциации, где внешний ключ для одно-к-одному отношения существует в целевой модели.
const User = sequelize.define('user', {/* ... */})
const Project = sequelize.define('project', {/* ... */})
// One-way associations
Project.hasOne(User)
/*
In this example hasOne will add an attribute projectId to the User model!
Furthermore, Project.prototype will gain the methods getUser and setUser according
to the first parameter passed to define. If you have underscore style
enabled, the added attribute will be project_id instead of projectId.
The foreign key will be placed on the users table.
You can also define the foreign key, e.g. if you already have an existing
database and want to work on it:
*/
Project.hasOne(User, { foreignKey: 'initiator_id' })
/*
Because Sequelize will use the model's name (first parameter of define) for
the accessor methods, it is also possible to pass a special option to hasOne:
*/
Project.hasOne(User, { as: 'Initiator' })
// Now you will get Project.getInitiator and Project.setInitiator
// Or let's define some self references
const Person = sequelize.define('person', { /* ... */})
Person.hasOne(Person, {as: 'Father'})
// this will add the attribute FatherId to Person
// also possible:
Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'})
// this will add the attribute DadId to Person
// In both cases you will be able to do:
Person.setFather
Person.getFather
// If you need to join a table twice you can double join the same table
Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'});
Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'});
Game.belongsTo(Team);
Несмотря на то, что это называется ассоциацией HasOne, для большинства отношений 1:1 вы обычно хотите использовать ассоциацию BelongsTo, так как BelongsTo добавит внешний ключ к источнику, а HasOne — к цели.
Разница между HasOne и BelongsTo
В Sequelize отношение 1:1 можно задать с помощью HasOne и BelongsTo. Они подходят для разных сценариев. Давайте изучим эту разницу с помощью примера.
Предположим, у нас есть две таблицы для связи Игрока и Команды. Давайте определим их модели.
const Player = this.sequelize.define('player', {/* attributes */})
const Team = this.sequelize.define('team', {/* attributes */});
Когда мы связываем две модели в Sequelize, мы можем ссылаться на них как на пары источника и цели моделей. Вот так:
С Игроком как источником и Командой как целью
Player.belongsTo(Team);
//Or
Player.hasOne(Team);
С Командой как источником и Игроком как целью
Team.belongsTo(Player);
//Or
Team.hasOne(Player);
HasOne и BelongsTo вставляют ключ ассоциации в разные модели друг от друга. HasOne вставляет ключ ассоциации в целевую модель, а BelongsTo — в исходную модель.
Вот пример, демонстрирующий варианты использования BelongsTo и HasOne.
const Player = this.sequelize.define('player', {/* attributes */})
const Coach = this.sequelize.define('coach', {/* attributes */})
const Team = this.sequelize.define('team', {/* attributes */});
Предположим, что наша Player модель содержит информацию о своей команде в столбце teamId. Информация о каждой команде Coach хранится в модели Team в столбце coachId. Оба этих сценария требуют разного типа 1:1 отношения, потому что внешний ключ отношения присутствует в разных моделях каждый раз.
Когда информация об ассоциации находится в источниковой модели, мы можем использовать belongsTo. В этом случае Player подходит для belongsTo, потому что у неё есть столбец teamId.
Player.belongsTo(Team) // `teamId` will be added on Player / Source model
Когда информация об ассоциации находится в целевой модели, мы можем использовать hasOne. В этом случае Coach подходит для hasOne, потому что модель Team хранит информацию о своей команде в поле coachId.
Coach.hasOne(Team) // `coachId` will be added on Team / Target model
Одно-ко-многим ассоциации (hasMany)
Одно-ко-многим ассоциации связывают один источник с несколькими целями. Однако цели снова связаны ровно с одним конкретным источником.
const User = sequelize.define('user', {/* ... */})
const Project = sequelize.define('project', {/* ... */})
// OK. Now things get more complicated (not really visible to the user :)).
// First let's define a hasMany association
Project.hasMany(User, {as: 'Workers'})
Это добавит атрибут projectId или project_id к User. Экземпляры Project получат методы доступа getWorkers и setWorkers.
Иногда вам может потребоваться связать записи в разных столбцах. Вы можете использовать опцию sourceKey:
const City = sequelize.define('city', { countryCode: Sequelize.STRING });
const Country = sequelize.define('country', { isoCode: Sequelize.STRING });
// Here we can connect countries and cities base on country code
Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'});
City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});
До сих пор мы работали с односторонней ассоциацией. Но мы хотим большего! Давайте определим её в другом направлении, создав многие-ко-многим ассоциацию в следующем разделе.
Многие-ко-многим ассоциации
Многие-ко-многим ассоциации используются для связи источников с несколькими целями. Кроме того, цели также могут иметь связи с несколькими источниками.
Project.belongsToMany(User, {through: 'UserProject'});
User.belongsToMany(Project, {through: 'UserProject'});
Это создаст новую модель под названием UserProject с соответствующими внешними ключами projectId и userId. Будут ли атрибуты в формате camelCase или нет, зависит от двух моделей, соединённых через таблицу (в данном случае User и Project).
Определение through является обязательным. Раньше Sequelize пытался автоматически генерировать имена, но это не всегда приводило к наиболее логичным настройкам.
Это добавит методы getUsers, setUsers, addUser, addUsers к Project, и getProjects, setProjects, addProject, и addProjects к User.
Иногда вам может потребоваться переименовать ваши модели при их использовании в ассоциациях. Давайте переименуем пользователей в сотрудников, а проекты в задачи, используя опцию алиаса (as). Мы также вручную определим используемые внешние ключи:
User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' })
Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })
foreignKey позволит вам установить ключ источника модели в отношении через. otherKey позволит вам установить ключ цели модели в отношении через.
User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'})
Конечно, вы также можете определить самоссылку с помощью belongsToMany:
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' })
// This will create the table PersonChildren which stores the ids of the objects.
Если вам нужны дополнительные атрибуты в таблице соединения, вы можете определить модель для таблицы соединения в sequelize до определения ассоциации, а затем сказать sequelize, что она должна использовать эту модель для соединения вместо создания новой:
const User = sequelize.define('user', {})
const Project = sequelize.define('project', {})
const UserProjects = sequelize.define('userProjects', {
status: DataTypes.STRING
})
User.belongsToMany(Project, { through: UserProjects })
Project.belongsToMany(User, { through: UserProjects })
Чтобы добавить новый проект к пользователю и установить его статус, вы передаёте дополнительные options.through в установщик, который содержит атрибуты для таблицы соединения.
user.addProject(project, { through: { status: 'started' }})
По умолчанию приведенный выше код добавит projectId и userId в таблицу UserProjects и удалит все ранее определенные атрибуты первичного ключа. Таблица будет уникально идентифицироваться комбинацией ключей двух таблиц, и нет причин иметь другие столбцы PK. Чтобы принудительно установить первичный ключ на модели UserProjects , вы можете добавить его вручную.
const UserProjects = sequelize.define('userProjects', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
status: DataTypes.STRING
})
С помощью Belongs-To-Many вы можете выполнять запросы, основанные на отношении через, и выбирать определённые атрибуты. Например, используя findAll с через
User.findAll({
include: [{
model: Project,
through: {
attributes: ['createdAt', 'startedAt', 'finishedAt'],
where: {completed: true}
}
}]
});
Belongs-To-Many создаёт уникальный ключ, когда первичный ключ отсутствует в модели через. Это имя уникального ключа можно переопределить с помощью опции uniqueKey.
Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' })
Области
Этот раздел касается областей видимости ассоциаций. Для определения областей видимости ассоциаций по сравнению с областями видимости связанных моделей см. Области видимости.
Области видимости ассоциаций позволяют разместить область видимости (набор атрибутов по умолчанию для get и create) на ассоциации. Области видимости могут быть размещены как на связанной модели (цели ассоциации), так и на таблице через для отношений n:m.
1:m
Предположим, у нас есть таблицы Комментарий, Пост и Изображение. Комментарий может быть связан либо с изображением, либо с постом через commentable_id и commentable — мы говорим, что Пост и Изображение являются Commentable.
const Comment = this.sequelize.define('comment', {
title: Sequelize.STRING,
commentable: Sequelize.STRING,
commentable_id: Sequelize.INTEGER
});
Comment.prototype.getItem = function(options) {
return this['get' + this.get('commentable').substr(0, 1).toUpperCase() + this.get('commentable').substr(1)](options);
};
Post.hasMany(this.Comment, {
foreignKey: 'commentable_id',
constraints: false,
scope: {
commentable: 'post'
}
});
Comment.belongsTo(this.Post, {
foreignKey: 'commentable_id',
constraints: false,
as: 'post'
});
Image.hasMany(this.Comment, {
foreignKey: 'commentable_id',
constraints: false,
scope: {
commentable: 'image'
}
});
Comment.belongsTo(this.Image, {
foreignKey: 'commentable_id',
constraints: false,
as: 'image'
});
constraints: false, отключает ограничения ссылок — поскольку столбец commentable_id ссылается на несколько таблиц, мы не можем добавить к нему ограничение REFERENCES. Обратите внимание, что отношения Изображение -> Комментарий и Пост -> Комментарий определяют область видимости, commentable: 'image' и commentable: 'post' соответственно. Эта область видимости автоматически применяется при использовании функций ассоциации:
image.getComments()
SELECT * FROM comments WHERE commentable_id = 42 AND commentable = 'image';
image.createComment({
title: 'Awesome!'
})
INSERT INTO comments (title, commentable_id, commentable) VALUES ('Awesome!', 42, 'image');
image.addComment(comment);
UPDATE comments SET commentable_id = 42, commentable = 'image'
Функция getItem в Comment завершает картину — она просто преобразует строку commentable в вызов getImage или getPost, предоставляя абстракцию над тем, относится ли комментарий к посту или изображению. Вы можете передать обычный объект параметров в getItem(options) для указания условий where или include.
n:m
Продолжая идею полиморфной модели, рассмотрим таблицу тегов — элемент может иметь несколько тегов, а тег может быть связан с несколькими элементами.
Для краткости в примере показана только модель Post, но на самом деле Tag будет связан с несколькими другими моделями.
const ItemTag = sequelize.define('item_tag', {
id : {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tag_id: {
type: DataTypes.INTEGER,
unique: 'item_tag_taggable'
},
taggable: {
type: DataTypes.STRING,
unique: 'item_tag_taggable'
},
taggable_id: {
type: DataTypes.INTEGER,
unique: 'item_tag_taggable',
references: null
}
});
const Tag = sequelize.define('tag', {
name: DataTypes.STRING
});
Post.belongsToMany(Tag, {
through: {
model: ItemTag,
unique: false,
scope: {
taggable: 'post'
}
},
foreignKey: 'taggable_id',
constraints: false
});
Tag.belongsToMany(Post, {
through: {
model: ItemTag,
unique: false
},
foreignKey: 'tag_id',
constraints: false
});
Обратите внимание, что столбец области видимости (taggable) теперь находится в модели через (ItemTag).
Мы также могли бы определить более жёсткую ассоциацию, например, чтобы получить все ожидающие теги для поста, применяя область видимости как модели через (ItemTag), так и целевой модели (Tag):
Post.hasMany(Tag, {
through: {
model: ItemTag,
unique: false,
scope: {
taggable: 'post'
}
},
scope: {
status: 'pending'
},
as: 'pendingTags',
foreignKey: 'taggable_id',
constraints: false
});
Post.getPendingTags();
SELECT `tag`.* INNER JOIN `item_tags` AS `item_tag`
ON `tag`.`id` = `item_tag`.`tagId`
AND `item_tag`.`taggable_id` = 42
AND `item_tag`.`taggable` = 'post'
WHERE (`tag`.`status` = 'pending');
constraints: false отключает ограничения ссылок на столбце taggable_id. Поскольку столбец полиморфный, мы не можем сказать, что он REFERENCES конкретной таблице.
Стратегия именования
По умолчанию sequelize будет использовать имя модели (переданное в sequelize.define ), чтобы определить имя модели при использовании в ассоциациях. Например, модель с именем user добавит функции get/set/add User к экземплярам связанной модели и свойство с именем .user при ленивой загрузке, в то время как модель с именем User добавит те же функции, но свойство с именем .User (обратите внимание на заглавную U) при ленивой загрузке.
Как мы уже видели, вы можете использовать псевдонимы моделей в ассоциациях с помощью as. В одиночных ассоциациях (has one и belongs to) псевдоним должен быть единственного числа, а для множественных ассоциаций (has many) — множественного числа. Sequelize затем использует библиотеку inflection для преобразования псевдонима в его единственное число. Однако это может не всегда работать для нерегулярных или неанглийских слов. В этом случае вы можете указать как множественную, так и единственную форму псевдонима:
User.belongsToMany(Project, { as: { singular: 'task', plural: 'tasks' }})
// Notice that inflection has no problem singularizing tasks, this is just for illustrative purposes.
Если вам известно, что модель всегда будет использовать тот же псевдоним в ассоциациях, вы можете указать его при создании модели
const Project = sequelize.define('project', attributes, {
name: {
singular: 'task',
plural: 'tasks',
}
})
User.belongsToMany(Project);
Это добавит функции add/set/get Tasks к экземплярам пользователя.
Помните, что использование as для изменения имени ассоциации также изменит имя внешнего ключа. При использовании as, безопаснее также указать внешний ключ.
Invoice.belongsTo(Subscription)
Subscription.hasMany(Invoice)
Без as, это добавляет subscriptionId как ожидается. Однако, если вы укажете Invoice.belongsTo(Subscription, { as: 'TheSubscription' }), у вас будут как subscriptionId, так и theSubscriptionId, потому что sequelize недостаточно умён, чтобы понять, что вызовы являются двумя сторонами одного отношения. `foreignKey` решает эту проблему;
Invoice.belongsTo(Subscription, , { as: 'TheSubscription', foreignKey: 'subscription_id' })
Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' )
Связывание объектов
Поскольку Sequelize выполняет много магических действий, вам необходимо вызвать Sequelize.sync после настройки ассоциаций! Это позволит вам следующее:
Project.hasMany(Task)
Task.belongsTo(Project)
Project.create()...
Task.create()...
Task.create()...
// save them... and then:
project.setTasks([task1, task2]).then(() => {
// saved!
})
// ok, now they are saved... how do I get them later on?
project.getTasks().then(associatedTasks => {
// associatedTasks is an array of tasks
})
// You can also pass filters to the getter method.
// They are equal to the options you can pass to a usual finder method.
project.getTasks({ where: 'id > 10' }).then(tasks => {
// tasks with an id greater than 10 :)
})
// You can also only retrieve certain fields of a associated object.
project.getTasks({attributes: ['title']}).then(tasks => {
// retrieve tasks with the attributes "title" and "id"
})
Чтобы удалить созданные ассоциации, вы можете просто вызвать метод `set` без указания конкретного идентификатора:
// remove the association with task1
project.setTasks([task2]).then(associatedTasks => {
// you will get task2 only
})
// remove 'em all
project.setTasks([]).then(associatedTasks => {
// you will get an empty array
})
// or remove 'em more directly
project.removeTask(task1).then(() => {
// it's gone
})
// and add 'em again
project.addTask(task1).then(function() {
// it's back again
})
Конечно, вы также можете сделать это наоборот:
// project is associated with task1 and task2
task2.setProject(null).then(function() {
// and it's gone
})
Для hasOne/belongsTo это в основном то же самое:
Task.hasOne(User, {as: "Author"})
Task.setAuthor(anAuthor)
Добавление ассоциаций к отношению с пользовательской таблицей соединения может быть выполнено двумя способами (продолжая с ассоциациями, определёнными в предыдущей главе):
// Either by adding a property with the name of the join table model to the object, before creating the association
project.UserProjects = {
status: 'active'
}
u.addProject(project)
// Or by providing a second options.through argument when adding the association, containing the data that should go in the join table
u.addProject(project, { through: { status: 'active' }})
// When associating multiple objects, you can combine the two options above. In this case the second argument
// will be treated as a defaults object, that will be used if no data is provided
project1.UserProjects = {
status: 'inactive'
}
u.setProjects([project1, project2], { through: { status: 'active' }})
// The code above will record inactive for project one, and active for project two in the join table
При получении данных по ассоциации, имеющей пользовательскую таблицу соединения, данные из таблицы соединения будут возвращены как экземпляр DAO:
u.getProjects().then(projects => {
const project = projects[0]
if (project.UserProjects.status === 'active') {
// .. do magic
// since this is a real DAO instance, you can save it directly after you are done doing magic
return project.UserProjects.save()
}
})
Если вам нужны только некоторые атрибуты из таблицы соединения, вы можете указать массив с нужными атрибутами:
// This will select only name from the Projects table, and only status from the UserProjects table
user.getProjects({ attributes: ['name'], joinTableAttributes: ['status']})
Проверка ассоциаций
Вы также можете проверить, связан ли объект уже с другим (только N:M). Вот как это сделать:
// check if an object is one of associated ones:
Project.create({ /* */ }).then(project => {
return User.create({ /* */ }).then(user => {
return project.hasUser(user).then(result => {
// result would be false
return project.addUser(user).then(() => {
return project.hasUser(user).then(result => {
// result would be true
})
})
})
})
})
// check if all associated objects are as expected:
// let's assume we have already a project and two users
project.setUsers([user1, user2]).then(() => {
return project.hasUsers([user1]);
}).then(result => {
// result would be true
return project.hasUsers([user1, user2]);
}).then(result => {
// result would be true
})
Внешние ключи
Когда вы создаёте ассоциации между вашими моделями в sequelize, ссылки на внешние ключи с ограничениями будут автоматически созданы. Настройка ниже:
const Task = this.sequelize.define('task', { title: Sequelize.STRING })
const User = this.sequelize.define('user', { username: Sequelize.STRING })
User.hasMany(Task)
Task.belongsTo(User)
Сгенерирует следующий SQL:
CREATE TABLE IF NOT EXISTS `User` (
`id` INTEGER PRIMARY KEY,
`username` VARCHAR(255)
);
CREATE TABLE IF NOT EXISTS `Task` (
`id` INTEGER PRIMARY KEY,
`title` VARCHAR(255),
`user_id` INTEGER REFERENCES `User` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
);
Отношение между задачей и пользователем вставляет внешний ключ user_id в таблицу задач и отмечает его как ссылку на таблицу User. По умолчанию user_id будет установлено в значение NULL, если связанный пользователь удалён, и обновлено, если идентификатор пользователя обновлён. Эти параметры можно переопределить, передав параметры onUpdate и onDelete в вызовы ассоциации. Валидационные параметры — RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL.
Для ассоциаций 1:1 и 1:m значение по умолчанию для удаления — SET NULL, а для обновлений — CASCADE. Для n:m значение по умолчанию для обоих параметров — CASCADE. Это означает, что если вы удаляете или обновляете строку с одной стороны ассоциации n:m, все строки в таблице соединения, ссылающиеся на эту строку, также будут удалены или обновлены.
Добавление ограничений между таблицами означает, что таблицы должны быть созданы в базе данных в определённом порядке при использовании sequelize.sync. Если у Task есть ссылка на User, таблица User должна быть создана до того, как может быть создана таблица Task. Это иногда может привести к циклическим ссылкам, где Sequelize не может найти порядок синхронизации. Представьте себе сценарий документов и версий. Документ может иметь несколько версий, и для удобства документ ссылается на свою текущую версию.
const Document = this.sequelize.define('document', {
author: Sequelize.STRING
})
const Version = this.sequelize.define('version', {
timestamp: Sequelize.DATE
})
Document.hasMany(Version) // This adds document_id to version
Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id'}) // This adds current_version_id to document
Однако код выше приведёт к следующей ошибке: Cyclic dependency found. 'Document' is dependent of itself. Dependency Chain: Document -> Version => Document. Чтобы устранить её, мы можем передать constraints: false одной из ассоциаций:
Document.hasMany(Version)
Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id', constraints: false})
Что позволит нам правильно синхронизировать таблицы:
CREATE TABLE IF NOT EXISTS `Document` (
`id` INTEGER PRIMARY KEY,
`author` VARCHAR(255),
`current_version_id` INTEGER
);
CREATE TABLE IF NOT EXISTS `Version` (
`id` INTEGER PRIMARY KEY,
`timestamp` DATETIME,
`document_id` INTEGER REFERENCES `Document` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
);
Принудительная ссылка на внешний ключ без ограничений
Иногда вам может потребоваться ссылаться на другую таблицу без добавления ограничений или ассоциаций. В этом случае вы можете вручную добавить атрибуты ссылки в определение вашей схемы и отметить отношения между ними.
// Series has a trainer_id=Trainer.id foreign reference key after we call Trainer.hasMany(series)
const Series = sequelize.define('series', {
title: DataTypes.STRING,
sub_title: DataTypes.STRING,
description: DataTypes.TEXT,
// Set FK relationship (hasMany) with `Trainer`
trainer_id: {
type: DataTypes.INTEGER,
references: {
model: "trainer",
key: "id"
}
}
})
const Trainer = sequelize.define('trainer', {
first_name: DataTypes.STRING,
last_name: DataTypes.STRING
});
// Video has a series_id=Series.id foreign reference key after we call Series.hasOne(Video)...
const Video = sequelize.define('video', {
title: DataTypes.STRING,
sequence: DataTypes.INTEGER,
description: DataTypes.TEXT,
// set relationship (hasOne) with `Series`
series_id: {
type: DataTypes.INTEGER,
references: {
model: Series, // Can be both a string representing the table name, or a reference to the model
key: "id"
}
}
});
Series.hasOne(Video);
Trainer.hasMany(Series);
Создание с ассоциациями
Экземпляр может быть создан со вложенной ассоциацией за один шаг, при условии, что все элементы являются новыми.
Создание элементов ассоциации "BelongsTo", "Has Many" или "HasOne"
Рассмотрим следующие модели:
const Product = this.sequelize.define('product', {
title: Sequelize.STRING
});
const User = this.sequelize.define('user', {
first_name: Sequelize.STRING,
last_name: Sequelize.STRING
});
const Address = this.sequelize.define('address', {
type: Sequelize.STRING,
line_1: Sequelize.STRING,
line_2: Sequelize.STRING,
city: Sequelize.STRING,
state: Sequelize.STRING,
zip: Sequelize.STRING,
});
Product.User = Product.belongsTo(User);
User.Addresses = User.hasMany(Address);
// Also works for `hasOne`
Новый Product, User, и один или несколько Address могут быть созданы за один шаг следующим образом:
return Product.create({
title: 'Chair',
user: {
first_name: 'Mick',
last_name: 'Broadstone',
addresses: [{
type: 'home',
line_1: '100 Main St.',
city: 'Austin',
state: 'TX',
zip: '78704'
}]
}
}, {
include: [{
association: Product.User,
include: [ User.Addresses ]
}]
});
Здесь наша модель пользователя называется user, с маленькой буквой u — это означает, что свойство в объекте также должно быть user. Если имя, данное sequelize.define было User, ключ в объекте также должен быть User. Аналогично для addresses, за исключением того, что это множественная ассоциация hasMany.
Создание элементов ассоциации "BelongsTo" с псевдонимом
Предыдущий пример может быть расширен для поддержки псевдонима ассоциации.
const Creator = Product.belongsTo(User, {as: 'creator'});
return Product.create({
title: 'Chair',
creator: {
first_name: 'Matt',
last_name: 'Hansen'
}
}, {
include: [ Creator ]
});
Создание элементов ассоциации "HasMany" или "BelongsToMany"
Давайте добавим возможность связать продукт со многими тегами. Настройка моделей может выглядеть так:
const Tag = this.sequelize.define('tag', {
name: Sequelize.STRING
});
Product.hasMany(Tag);
// Also works for `belongsToMany`.
Теперь мы можем создать продукт с несколькими тегами следующим образом:
Product.create({
id: 1,
title: 'Chair',
tags: [
{ name: 'Alpha'},
{ name: 'Beta'}
]
}, {
include: [ Tag ]
})
И мы можем изменить этот пример, чтобы он также поддерживал псевдоним:
const Categories = Product.hasMany(Tag, {as: 'categories'});
Product.create({
id: 1,
title: 'Chair',
categories: [
{id: 1, name: 'Alpha'},
{id: 2, name: 'Beta'}
]
}, {
include: [{
model: Categories,
as: 'categories'
}]
})
Транзакции
Транзакции
Sequelize поддерживает два способа использования транзакций:
- Один, который автоматически подтвердит или откатит транзакцию на основе результата цепочки обещаний и (если включено) передаст транзакцию всем вызовам внутри обратного вызова
- И другой, который оставляет подтверждение, откат и передачу транзакции пользователю.
Ключевое отличие заключается в том, что управляемая транзакция использует обратный вызов, который ожидает возврата обещания, в то время как неуправляемая транзакция возвращает обещание.
Управляемая транзакция (автоматический обратный вызов)
Управляемые транзакции обрабатывают подтверждение или откат транзакции автоматически. Вы начинаете управляемую транзакцию, передавая обратный вызов sequelize.transaction.
Обратите внимание, как обратный вызов, переданный transaction, возвращает цепочку обещаний и не вызывает явно t.commit() ни t.rollback(). Если все обещания в возвращенной цепочке разрешены успешно, транзакция подтверждается. Если одно или несколько обещаний отклонены, транзакция отменяется.
return sequelize.transaction(function (t) {
// chain all your queries here. make sure you return them.
return User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, {transaction: t}).then(function (user) {
return user.setShooter({
firstName: 'John',
lastName: 'Boothe'
}, {transaction: t});
});
}).then(function (result) {
// Transaction has been committed
// result is whatever the result of the promise chain returned to the transaction callback
}).catch(function (err) {
// Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback
});
Выбрасывание ошибок для отката
При использовании управляемой транзакции вы никогда не должны вручную подтверждать или отменять транзакцию. Если все запросы выполнены успешно, но вы все равно хотите отменить транзакцию (например, из-за ошибки валидации), вы должны выбросить ошибку, чтобы разорвать и отклонить цепочку:
return sequelize.transaction(function (t) {
return User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, {transaction: t}).then(function (user) {
// Woops, the query was successful but we still want to roll back!
throw new Error();
});
});
Автоматическая передача транзакций всем запросам
В приведенных выше примерах транзакция все еще передается вручную, передавая { transaction: t } в качестве второго аргумента. Чтобы автоматически передавать транзакцию всем запросам, необходимо установить модуль хранилища контекста продолжения (CLS) и создать пространство имен в собственном коде:
const cls = require('continuation-local-storage'),
namespace = cls.createNamespace('my-very-own-namespace');
Для включения CLS необходимо указать Sequelize, какое пространство имен использовать, используя статический метод конструктора sequelize:
const Sequelize = require('sequelize');
Sequelize.useCLS(namespace);
new Sequelize(....);
Обратите внимание, что метод useCLS() находится в конструкторе, а не в экземпляре sequelize. Это означает, что все экземпляры будут использовать одно и то же пространство имен, и что CLS работает по принципу «все или ничего» — вы не можете включить его только для некоторых экземпляров.
CLS работает как хранилище локальных переменных для обратных вызовов. На практике это означает, что различные цепочки обратных вызовов могут получать доступ к локальным переменным, используя пространство имен CLS. При включенном CLS Sequelize установит свойство transaction в пространстве имен при создании новой транзакции. Поскольку переменные, установленные в цепочке обратных вызовов, являются частными для этой цепочки, несколько одновременных транзакций могут существовать одновременно:
sequelize.transaction(function (t1) {
namespace.get('transaction') === t1; // true
});
sequelize.transaction(function (t2) {
namespace.get('transaction') === t2; // true
});
В большинстве случаев вам не нужно обращаться к namespace.get('transaction') напрямую, так как все запросы автоматически будут искать транзакцию в пространстве имен:
sequelize.transaction(function (t1) {
// With CLS enabled, the user will be created inside the transaction
return User.create({ name: 'Alice' });
});
После использования Sequelize.useCLS() все обещания, возвращаемые из Sequelize, будут исправлены для поддержания контекста CLS. CLS — сложная тема; более подробную информацию см. в документации по cls-bluebird, используемому исправлению, чтобы сделать обещания Bluebird совместимыми с CLS.
Примечание: _CLS в настоящее время поддерживает только async/await при использовании пакета cls-hooked. Хотя cls-hooked опирается на экспериментальный API async_hooks_
Конкурентные/частичные транзакции
Вы можете иметь конкурирующие транзакции в последовательности запросов или исключить некоторые из них из любых транзакций. Используйте параметр {transaction: } для управления транзакцией, к которой относится запрос:
Предупреждение: SQLite не поддерживает более одной транзакции одновременно.
Без включенного CLS
sequelize.transaction(function (t1) {
return sequelize.transaction(function (t2) {
// With CLS enable, queries here will by default use t2
// Pass in the `transaction` option to define/alter the transaction they belong to.
return Promise.all([
User.create({ name: 'Bob' }, { transaction: null }),
User.create({ name: 'Mallory' }, { transaction: t1 }),
User.create({ name: 'John' }) // this would default to t2
]);
});
});
Уровни изоляции
Возможные уровни изоляции для использования при запуске транзакции:
Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED"
Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED"
Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ"
Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE"
По умолчанию Sequelize использует уровень изоляции базы данных. Если вы хотите использовать другой уровень изоляции, передайте желаемый уровень в качестве первого аргумента:
return sequelize.transaction({
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
}, function (t) {
// your transactions
});
Примечание: Запросы SET ISOLATION LEVEL не регистрируются в случае MSSQL, так как указанный isolationLevel передаётся напрямую в tedious
Неуправляемая транзакция (обратный вызов then)
Неуправляемые транзакции требуют от вас ручного отката или подтверждения транзакции. Если вы этого не сделаете, транзакция будет зависать, пока не истечет время ожидания. Чтобы начать неуправляемую транзакцию, вызовите sequelize.transaction() без обратного вызова (вы по-прежнему можете передать объект опций) и вызовите then на возвращенном обещании. Обратите внимание, что commit() и rollback() возвращают обещание.
return sequelize.transaction().then(function (t) {
return User.create({
firstName: 'Bart',
lastName: 'Simpson'
}, {transaction: t}).then(function (user) {
return user.addSibling({
firstName: 'Lisa',
lastName: 'Simpson'
}, {transaction: t});
}).then(function () {
return t.commit();
}).catch(function (err) {
return t.rollback();
});
});
Опции
Метод transaction может вызываться с объектом опций в качестве первого аргумента, что позволяет настроить транзакцию.
return sequelize.transaction({ /* options */ });
Доступны следующие опции (с их значениями по умолчанию):
{
autocommit: true,
isolationLevel: 'REPEATABLE_READ',
deferrable: 'NOT DEFERRABLE' // implicit default of postgres
}
Параметр isolationLevel можно установить глобально при инициализации экземпляра Sequelize или локально для каждой транзакции:
// globally
new Sequelize('db', 'user', 'pw', {
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});
// locally
sequelize.transaction({
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});
Параметр deferrable запускает дополнительный запрос после начала транзакции, который необязательно устанавливает проверки ограничений как отложенные, так и непосредственные. Обратите внимание, что это поддерживается только в PostgreSQL.
sequelize.transaction({
// to defer all constraints:
deferrable: Sequelize.Deferrable.SET_DEFERRED,
// to defer a specific constraint:
deferrable: Sequelize.Deferrable.SET_DEFERRED(['some_constraint']),
// to not defer constraints:
deferrable: Sequelize.Deferrable.SET_IMMEDIATE
})
Использование с другими методами Sequelize
Параметр transaction сочетается с большинством других параметров, которые обычно являются первым аргументом метода. Для методов, принимающих значения, таких как .create, .update(), .updateAttributes() и т. д., transaction необходимо передать во второй аргумент. Если вы не уверены, обратитесь к документации API для используемого вами метода, чтобы убедиться в правильности сигнатуры.
Обработчик после подтверждения
Объект transaction позволяет отслеживать, подтверждена ли транзакция и когда.
Обработчик afterCommit можно добавить как в управляемые, так и в неуправляемые объекты транзакций:
sequelize.transaction(t => {
t.afterCommit((transaction) => {
// Your logic
});
});
sequelize.transaction().then(t => {
t.afterCommit((transaction) => {
// Your logic
});
return t.commit();
})
Функция, переданная в afterCommit, может необязательно возвращать обещание, которое разрешится до того, как разрешится цепочка обещаний, создавшая транзакцию
Обработчики afterCommit не вызываются, если транзакция отменена.
Обработчики afterCommit не изменяют возвращаемое значение транзакции, в отличие от стандартных обработчиков.
Вы можете использовать обработчик afterCommit в сочетании с обработчиками моделей, чтобы узнать, когда экземпляр сохранён и доступен вне транзакции.
```js model.afterSave((instance, options) => { if (options.transaction) { // Сохранение выполнено внутри транзакции, подождите, пока транзакция не подтвердится, чтобы // уведомить слушателей, что экземпляр сохранён options.transaction.afterCommit(() => / Уведомить /) return; } // Сохранение выполнено вне транзакции, безопасно для вызывающих сторон получать обновленную модель // Уведомить
Области
Области
Области позволяют определять часто используемые запросы, которые можно легко использовать позднее. Области могут включать все те же атрибуты, что и обычные finders, where, include, limit и т. д.
Определение
Области определяются в определении модели и могут быть объектами finder или функциями, возвращающими объекты finder — за исключением стандартной области, которая может быть только объектом:
const Project = sequelize.define('project', {
// Attributes
}, {
defaultScope: {
where: {
active: true
}
},
scopes: {
deleted: {
where: {
deleted: true
}
},
activeUsers: {
include: [
{ model: User, where: { active: true }}
]
},
random: function () {
return {
where: {
someNumber: Math.random()
}
}
},
accessLevel: function (value) {
return {
where: {
accessLevel: {
[Op.gte]: value
}
}
}
}
}
});
Вы также можете добавить области после определения модели, вызвав addScope. Это особенно полезно для областей с вложениями, где модель в вложении может быть не определена в момент определения другой модели.
Стандартная область всегда применяется. Это означает, что с указанным выше определением модели Project.findAll() создаст следующий запрос:
SELECT * FROM projects WHERE active = true
Стандартная область может быть удалена, вызвав .unscoped(), .scope(null), или вызвав другую область:
Project.scope('deleted').findAll(); // Removes the default scope
SELECT * FROM projects WHERE deleted = true
Также можно включить модели в области в определении области. Это позволяет избежать дублирования include, attributes или where определений. Используя приведенный выше пример и вызывая область active для модели User (вместо указания условия непосредственно в этом объекте вложения):
activeUsers: {
include: [
{ model: User.scope('active')}
]
}
Использование
Области применяются путем вызова .scope на определении модели, передавая имя одной или нескольких областей. .scope возвращает полностью функциональный экземпляр модели со всеми стандартными методами: .findAll, .update, .count, .destroy и т. д. Вы можете сохранить этот экземпляр модели и повторно использовать его позже:
const DeletedProjects = Project.scope('deleted');
DeletedProjects.findAll();
// some time passes
// let's look for deleted projects again!
DeletedProjects.findAll();
Области применяются к .find, .findAll, .count, .update, .increment и .destroy.
Области, которые являются функциями, можно вызывать двумя способами. Если область не принимает аргументов, ее можно вызвать обычным образом. Если область принимает аргументы, передайте объект:
Project.scope('random', { method: ['accessLevel', 19]}).findAll();
SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19
Объединение
Несколько областей могут быть применены одновременно, передав массив областей в .scope, или передав области как последовательные аргументы.
// These two are equivalent
Project.scope('deleted', 'activeUsers').findAll();
Project.scope(['deleted', 'activeUsers']).findAll();
SELECT * FROM projects
INNER JOIN users ON projects.userId = users.id
AND users.active = true
Если вы хотите применить другую область вместе со стандартной областью, передайте ключ defaultScope в .scope.
Project.scope('defaultScope', 'deleted').findAll();
SELECT * FROM projects WHERE active = true AND deleted = true
При вызове нескольких областей ключи из последующих областей перезапишут предыдущие (аналогично _.assign). Рассмотрим две области:
{
scope1: {
where: {
firstName: 'bob',
age: {
[Op.gt]: 20
}
},
limit: 2
},
scope2: {
where: {
age: {
[Op.gt]: 30
}
},
limit: 10
}
}
Вызов .scope('scope1', 'scope2') даст следующий запрос:
WHERE firstName = 'bob' AND age > 30 LIMIT 10
Обратите внимание, как limit и age перезаписываются scope2, а firstName сохраняется. limit, offset, order, paranoid, lock и raw перезаписываются, в то время как where и include объединяются поверхностно. Это означает, что идентичные ключи в объектах where и последующие включения той же модели будут перезаписывать друг друга.
Та же логика объединения применяется при непосредственном передаче объекта find в метод findAll для модели с областью:
Project.scope('deleted').findAll({
where: {
firstName: 'john'
}
})
WHERE deleted = true AND firstName = 'john'
Здесь область deleted объединяется с поиском. Если мы передадим where: { firstName: 'john', deleted: false } в поиск, область deleted будет перезаписана.
Ассоциации
Sequelize имеет два разных, но связанных понятия областей в отношении ассоциаций. Разница тонкая, но важная:
-
Области ассоциаций позволяют указывать значения по умолчанию при получении и установке ассоциаций — полезно при реализации полиморфных ассоциаций. Эта область вызывается только при ассоциации между двумя моделями, при использовании функций связанных моделей
get,set,addиcreate. - Области связанных моделей позволяют применять области по умолчанию и другие области при получении ассоциаций, и позволяют передавать модель с областью при создании ассоциаций. Эти области применяются как к стандартным поискам модели, так и к поиску через ассоциацию.
Например, рассмотрим модели Post и Comment. Comment ассоциирована с несколькими другими моделями (Image, Video и т. д.), и ассоциация между Comment и другими моделями является полиморфной, что означает, что Comment хранит столбец commentable, помимо внешнего ключа commentable_id.
Полиморфная ассоциация может быть реализована с помощью области ассоциации:
this.Post.hasMany(this.Comment, {
foreignKey: 'commentable_id',
scope: {
commentable: 'post'
}
});
При вызове post.getComments() это автоматически добавит WHERE commentable = 'post'. Аналогично, при добавлении новых комментариев к посту, commentable автоматически установится в 'post'. Область ассоциации предназначена для работы в фоновом режиме, не требуя от программиста беспокоиться об этом — ее нельзя отключить. Более подробный пример полиморфной ассоциации см. в Области ассоциаций.
Предположим, что Post имеет область по умолчанию, которая отображает только активные посты: where: { active: true }. Эта область находится на связанной модели (Post), а не на ассоциации, как область commentable. Так же, как область по умолчанию применяется при вызове Post.findAll(), она применяется и при вызове User.getPosts() — это вернет только активные посты для данного пользователя.
Чтобы отключить область по умолчанию, передайте scope: null в метод-получатель: User.getPosts({ scope: null }). Аналогично, если вы хотите применить другие области, передайте массив, как вы передавали в .scope.
User.getPosts({ scope: ['scope1', 'scope2']});
Если вы хотите создать сокращенную функцию для области связанной модели, вы можете передать модель с областью в ассоциацию. Рассмотрим сокращенную функцию для получения всех удаленных постов пользователя:
const Post = sequelize.define('post', attributes, {
defaultScope: {
where: {
active: true
}
},
scopes: {
deleted: {
where: {
deleted: true
}
}
}
});
User.hasMany(Post); // regular getPosts association
User.hasMany(Post.scope('deleted'), { as: 'deletedPosts' });
User.getPosts(); // WHERE active = true
User.getDeletedPosts(); // WHERE deleted = true
Обработчики
Обработчики
Обработчики (также известные как события жизненного цикла) — это функции, которые вызываются до и после выполнения вызовов в sequelize. Например, если вы хотите всегда устанавливать значение в модели перед сохранением, вы можете добавить обработчик beforeUpdate.
Полный список обработчиков см. в файле Hooks file.
Порядок операций
(1)
beforeBulkCreate(instances, options)
beforeBulkDestroy(options)
beforeBulkUpdate(options)
(2)
beforeValidate(instance, options)
(-)
validate
(3)
afterValidate(instance, options)
- or -
validationFailed(instance, options, error)
(4)
beforeCreate(instance, options)
beforeDestroy(instance, options)
beforeUpdate(instance, options)
beforeSave(instance, options)
beforeUpsert(values, options)
(-)
create
destroy
update
(5)
afterCreate(instance, options)
afterDestroy(instance, options)
afterUpdate(instance, options)
afterSave(instance, options)
afterUpsert(created, options)
(6)
afterBulkCreate(instances, options)
afterBulkDestroy(options)
afterBulkUpdate(options)
Объявление обработчиков
Аргументы обработчиков передаются по ссылке. Это означает, что вы можете изменить значения, и это будет отражено в операторах вставки/обновления. Обработчик может содержать асинхронные действия — в этом случае функция обработчика должна возвращать promise.
В настоящее время есть три способа программного добавления обработчиков:
// Method 1 via the .define() method
const User = sequelize.define('user', {
username: DataTypes.STRING,
mood: {
type: DataTypes.ENUM,
values: ['happy', 'sad', 'neutral']
}
}, {
hooks: {
beforeValidate: (user, options) => {
user.mood = 'happy';
},
afterValidate: (user, options) => {
user.username = 'Toni';
}
}
});
// Method 2 via the .hook() method (or its alias .addHook() method)
User.hook('beforeValidate', (user, options) => {
user.mood = 'happy';
});
User.addHook('afterValidate', 'someCustomName', (user, options) => {
return sequelize.Promise.reject(new Error("I'm afraid I can't let you do that!"));
});
// Method 3 via the direct method
User.beforeCreate((user, options) => {
return hashPassword(user.password).then(hashedPw => {
user.password = hashedPw;
});
});
User.afterValidate('myHookAfter', (user, options) => {
user.username = 'Toni';
});
Удаление обработчиков
Можно удалить только обработчик с параметром name.
const Book = sequelize.define('book', {
title: DataTypes.STRING
});
Book.addHook('afterCreate', 'notifyUsers', (book, options) => {
// ...
});
Book.removeHook('afterCreate', 'notifyUsers');
Можно иметь несколько обработчиков с одинаковым именем. Вызов .removeHook() удалит все из них.
Глобальные/универсальные обработчики
Глобальные обработчики — это обработчики, которые выполняются для всех моделей. Они могут определять поведение, необходимое для всех моделей, и особенно полезны для плагинов. Они могут быть определены двумя способами, имеющими несколько отличную семантику:
Sequelize.options.define (обработчик по умолчанию)
const sequelize = new Sequelize(..., {
define: {
hooks: {
beforeCreate: () => {
// Do stuff
}
}
}
});
Это добавляет обработчик по умолчанию ко всем моделям, который выполняется, если модель не определяет свой собственный beforeCreate обработчик:
const User = sequelize.define('user');
const Project = sequelize.define('project', {}, {
hooks: {
beforeCreate: () => {
// Do other stuff
}
}
});
User.create() // Runs the global hook
Project.create() // Runs its own hook (because the global hook is overwritten)
Sequelize.addHook (постоянный обработчик)
sequelize.addHook('beforeCreate', () => {
// Do stuff
});
Этот обработчик всегда выполняется перед созданием, независимо от того, определяет ли модель свой собственный beforeCreate обработчик:
const User = sequelize.define('user');
const Project = sequelize.define('project', {}, {
hooks: {
beforeCreate: () => {
// Do other stuff
}
}
});
User.create() // Runs the global hook
Project.create() // Runs its own hook, followed by the global hook
Локальные обработчики всегда выполняются до глобальных.
Обработчики экземпляров
Следующие обработчики будут срабатывать всякий раз, когда вы редактируете один объект:
beforeValidate
afterValidate or validationFailed
beforeCreate / beforeUpdate / beforeDestroy
afterCreate / afterUpdate / afterDestroy
// ...define ...
User.beforeCreate(user => {
if (user.accessLevel > 10 && user.username !== "Boss") {
throw new Error("You can't grant this user an access level above 10!")
}
})
Этот пример вернет ошибку:
User.create({username: 'Not a Boss', accessLevel: 20}).catch(err => {
console.log(err); // You can't grant this user an access level above 10!
});
Следующий пример вернет успех:
User.create({username: 'Boss', accessLevel: 20}).then(user => {
console.log(user); // user object with username as Boss and accessLevel of 20
});
Обработчики моделей
Иногда вы будете редактировать более одной записи за раз, используя методы bulkCreate, update, destroy модели. Следующие обработчики будут срабатывать всякий раз, когда вы используете один из этих методов:
beforeBulkCreate(instances, options)
beforeBulkUpdate(options)
beforeBulkDestroy(options)
afterBulkCreate(instances, options)
afterBulkUpdate(options)
afterBulkDestroy(options)
Если вы хотите вызывать обработчики для каждой отдельной записи вместе с массовыми обработчиками, вы можете передать individualHooks: true в вызов.
Model.destroy({ where: {accessLevel: 0}, individualHooks: true});
// Will select all records that are about to be deleted and emit before- + after- Destroy on each instance
Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: true});
// Will select all records that are about to be updated and emit before- + after- Update on each instance
Аргумент options метода обработчика будет вторым аргументом, предоставленным соответствующему методу или его клонированной и расширенной версии.
Model.beforeBulkCreate((records, {fields}) => {
// records = the first argument sent to .bulkCreate
// fields = one of the second argument fields sent to .bulkCreate
})
Model.bulkCreate([
{username: 'Toni'}, // part of records argument
{username: 'Tobi'} // part of records argument
], {fields: ['username']} // options parameter
)
Model.beforeBulkUpdate(({attributes, where}) => {
// where - in one of the fields of the clone of second argument sent to .update
// attributes - is one of the fields that the clone of second argument of .update would be extended with
})
Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/)
Model.beforeBulkDestroy(({where, individualHooks}) => {
// individualHooks - default of overridden value of extended clone of second argument sent to Model.destroy
// where - in one of the fields of the clone of second argument sent to Model.destroy
})
Model.destroy({ where: {username: 'Tom'}} /*where argument*/)
Если вы используете Model.bulkCreate(...) с опцией updatesOnDuplicate, изменения, внесенные в обработчик в поля, которые не указаны в массиве updatesOnDuplicate, не будут сохранены в базе данных. Однако вы можете изменить опцию updatesOnDuplicate внутри обработчика, если это необходимо.
// Bulk updating existing users with updatesOnDuplicate option
Users.bulkCreate([
{ id: 1, isMember: true },
{ id: 2, isMember: false }
], {
updatesOnDuplicate: ['isMember']
});
User.beforeBulkCreate((users, options) => {
for (const user of users) {
if (user.isMember) {
user.memberSince = new Date();
}
}
// Add memberSince to updatesOnDuplicate otherwise the memberSince date wont be
// saved to the database
options.updatesOnDuplicate.push('memberSince');
});
Связи
В основном обработчики будут работать одинаково для экземпляров при ассоциации, за исключением нескольких моментов
- При использовании функций add/set будут выполняться обработчики beforeUpdate/afterUpdate.
- Единственный способ вызвать обработчики beforeDestroy/afterDestroy — это использовать связи с
onDelete: 'cascade'и опциейhooks: true. Например:
const Projects = sequelize.define('projects', {
title: DataTypes.STRING
});
const Tasks = sequelize.define('tasks', {
title: DataTypes.STRING
});
Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true });
Tasks.belongsTo(Projects);
Этот код выполнит beforeDestroy/afterDestroy для таблицы Tasks. Sequelize по умолчанию будет пытаться оптимизировать ваши запросы. При вызове каскадного удаления Sequelize просто выполнит
DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey
Однако, явно добавив hooks: true, вы указываете Sequelize, что оптимизация вам не нужна, и он выполнит SELECT для связанных объектов и удалит каждый экземпляр по одному, чтобы иметь возможность вызвать обработчики с правильными параметрами.
Если ваша ассоциация имеет тип n:m, вас может заинтересовать запуск обработчиков на модели через при использовании вызова remove. Внутренне sequelize использует Model.destroy, что приводит к вызову обработчика bulkDestroy вместо before/afterDestroy для каждого экземпляра через.
Это можно легко решить, передав {individualHooks: true} в вызов remove, что приведет к вызову каждого обработчика для каждого удаленного экземпляра объекта через.
Замечание по транзакциям
Обратите внимание, что многие операции с моделями в Sequelize позволяют указать транзакцию в параметре options метода. Если транзакция указана в исходном вызове, она будет присутствовать в параметре options, переданном функции обработчика. Например, рассмотрим следующий фрагмент:
// Here we use the promise-style of async hooks rather than
// the callback.
User.hook('afterCreate', (user, options) => {
// 'transaction' will be available in options.transaction
// This operation will be part of the same transaction as the
// original User.create call.
return User.update({
mood: 'sad'
}, {
where: {
id: user.id
},
transaction: options.transaction
});
});
sequelize.transaction(transaction => {
User.create({
username: 'someguy',
mood: 'happy',
transaction
});
});
Если бы мы не включили опцию транзакции в наш вызов User.update в предыдущем коде, изменений не произошло бы, так как наш только что созданный пользователь не существует в базе данных до тех пор, пока ожидающая транзакция не будет подтверждена.
Внутренние транзакции
Очень важно понимать, что sequelize может использовать транзакции внутри для определенных операций, таких как Model.findOrCreate. Если ваши функции обработчиков выполняют операции чтения или записи, которые полагаются на существование объекта в базе данных или изменяют хранимые значения объекта, как в предыдущем примере, вы всегда должны указывать { transaction: options.transaction }.
Если обработчик был вызван в процессе транзакционной операции, это гарантирует, что ваша зависимая операция чтения/записи является частью той же транзакции. Если обработчик не транзакционный, вы просто указали { transaction: null } и можете ожидать стандартного поведения.
Прямые запросы
Прямые запросы
Поскольку часто есть случаи, когда проще выполнить сырые/уже подготовленные SQL-запросы, вы можете использовать функцию sequelize.query.
По умолчанию функция возвращает два аргумента — массив результатов и объект, содержащий метаданные (затронутые строки и т. д.). Обратите внимание, что поскольку это прямой запрос, метаданные (имена свойств и т. д.) зависят от диалекта. Некоторые диалекты возвращают метаданные «внутри» объекта результатов (как свойства в массиве). Однако всегда будут возвращаться два аргумента, но для MSSQL и MySQL это будут две ссылки на один и тот же объект.
sequelize.query("UPDATE users SET y = 42 WHERE x = 12").spread((results, metadata) => {
// Results will be an empty array and metadata will contain the number of affected rows.
})
В случаях, когда вам не нужно обращаться к метаданным, вы можете передать тип запроса, чтобы сообщить sequelize, как форматировать результаты. Например, для простого запроса SELECT вы можете сделать:
sequelize.query("SELECT * FROM `users`", { type: sequelize.QueryTypes.SELECT})
.then(users => {
// We don't need spread here, since only the results will be returned for select queries
})
Доступно несколько других типов запросов. Посмотрите исходный код для получения подробностей.
Второй вариант — модель. Если вы передадите модель, возвращаемые данные будут экземплярами этой модели.
// Callee is the model definition. This allows you to easily map a query to a predefined model
sequelize
.query('SELECT * FROM projects', {
model: Projects,
mapToModel: true // pass true here if you have any mapped fields
})
.then(projects => {
// Each record will now be an instance of Project
})
Замены
Замены в запросе можно выполнить двумя способами: с использованием именованных параметров (начинающихся с :) или безымянных, представленных ?. Замены передаются в объекте options.
- Если передается массив,
?будут заменены в порядке их появления в массиве. - Если передается объект,
:keyбудут заменены ключами из этого объекта. Если объект содержит ключи, отсутствующие в запросе, или наоборот, будет выброшено исключение.
sequelize.query('SELECT * FROM projects WHERE status = ?',
{ replacements: ['active'], type: sequelize.QueryTypes.SELECT }
).then(projects => {
console.log(projects)
})
sequelize.query('SELECT * FROM projects WHERE status = :status ',
{ replacements: { status: 'active' }, type: sequelize.QueryTypes.SELECT }
).then(projects => {
console.log(projects)
})
Массовые замены будут автоматически обработаны, следующий запрос ищет проекты, где статус соответствует массиву значений.
sequelize.query('SELECT * FROM projects WHERE status IN(:status) ',
{ replacements: { status: ['active', 'inactive'] }, type: sequelize.QueryTypes.SELECT }
).then(projects => {
console.log(projects)
})
Чтобы использовать оператор подстановки %, добавьте его к вашей замене. Следующий запрос находит пользователей с именами, начинающимися с 'ben'.
sequelize.query('SELECT * FROM users WHERE name LIKE :search_name ',
{ replacements: { search_name: 'ben%' }, type: sequelize.QueryTypes.SELECT }
).then(projects => {
console.log(projects)
})
Параметры связи
Параметры связи подобны заменам. За исключением того, что замены экранируются и вставляются в запрос sequelize до отправки запроса в базу данных, а параметры связи отправляются в базу данных вне текста SQL-запроса. Запрос может содержать либо параметры связи, либо замены. Параметры связи обозначаются либо $1, $2,... (числовые), либо $key (альфа-числовые). Это независимо от диалекта.
- Если передается массив,
$1привязывается к первому элементу массива (bind[0]) - Если передается объект,
$keyпривязывается кobject['key']. Каждый ключ должен начинаться с нецифрового символа.$1не является допустимым ключом, даже еслиobject['1']существует. - В любом случае
$$может использоваться для экранирования буквального символа$.
Массив или объект должны содержать все значения привязки, иначе Sequelize выбросит исключение. Это относится даже к случаям, когда база данных может игнорировать параметр привязки.
База данных может добавить дополнительные ограничения к этому. Параметры связи не могут быть SQL-ключевыми словами, именами таблиц или столбцов. Они также игнорируются в кавычках или данных. В PostgreSQL также может потребоваться приведение типа, если тип нельзя определить из контекста $1::varchar.
sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $1',
{ bind: ['active'], type: sequelize.QueryTypes.SELECT }
).then(projects => {
console.log(projects)
})
sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $status',
{ bind: { status: 'active' }, type: sequelize.QueryTypes.SELECT }
).then(projects => {
console.log(projects)
})
Миграции
Миграции
Так же, как вы используете Git / SVN для управления изменениями в исходном коде, вы можете использовать миграции для отслеживания изменений в базе данных. С помощью миграций вы можете перенести вашу существующую базу данных в другое состояние и наоборот: эти переходы состояний сохраняются в файлах миграций, которые описывают, как перейти к новому состоянию и как отменить изменения, чтобы вернуться к старому состоянию.
Вам понадобится Sequelize CLI. CLI поддерживает миграции и создание проектов.
CLI
Установка CLI
Начнем с установки CLI, инструкции вы можете найти здесь. Наиболее предпочтительный способ — установка локально, так:
$ npm install --save sequelize-cli
Создание проекта
Для создания пустого проекта вам необходимо выполнить команду init
$ node_modules/.bin/sequelize init
Это создаст следующие папки:
-
config, содержит файл конфигурации, который сообщает CLI, как подключиться к базе данных -
models, содержит все модели вашего проекта -
migrations, содержит все файлы миграций -
seeders, содержит все файлы семян
Конфигурация
Прежде чем продолжить, нам нужно указать 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_test",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
Теперь отредактируйте этот файл и задайте правильные учетные данные базы данных и диалект.
Примечание: Если ваша база данных еще не существует, вы можете просто вызвать команду db:create. При правильном доступе она создаст для вас эту базу данных.
Создание первой модели (и миграции)
После правильной настройки файла конфигурации CLI вы готовы создать свою первую миграцию. Это так же просто, как выполнить простую команду.
Мы будем использовать команду model:generate. Эта команда требует двух параметров:
-
name, имя модели -
attributes, список атрибутов модели
Давайте создадим модель с именем User.
$ node_modules/.bin/sequelize model:generate --name User --attributes firstName:string,lastName:string,email:string
Это выполнит следующие действия:
- Создаст файл модели
userв папкеmodels - Создаст файл миграции с именем, похожим на
XXXXXXXXXXXXXX-create-user.jsв папкеmigrations
Примечание: Sequelize будет использовать только файлы моделей, это представление таблицы. С другой стороны, файл миграции — это изменение в этой модели или, точнее, в этой таблице, используемое CLI. Рассматривайте миграции как коммит или журнал некоторых изменений в базе данных.
Запуск миграций
До этого шага мы ничего не вставляли в базу данных. Мы только что создали необходимые файлы модели и миграции для нашей первой модели User. Теперь, чтобы фактически создать эту таблицу в базе данных, вам нужно выполнить команду db:migrate.
$ node_modules/.bin/sequelize db:migrate
Эта команда выполнит следующие действия:
- Создаст таблицу с именем
SequelizeMetaв базе данных. Эта таблица используется для записи того, какие миграции были выполнены в текущей базе данных - Начнет поиск любых файлов миграций, которые еще не были выполнены. Это возможно, проверяя таблицу
SequelizeMeta. В этом случае он запустит миграциюXXXXXXXXXXXXXX-create-user.js, которую мы создали на последнем шаге. - Создаст таблицу с именем
Usersсо всеми столбцами, как указано в ее файле миграции.
Отмена миграций
Теперь наша таблица была создана и сохранена в базе данных. С помощью миграции вы можете вернуться к старому состоянию, просто выполнив команду.
Вы можете использовать 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
Создание первого семени
Предположим, мы хотим вставить некоторые данные в несколько таблиц по умолчанию. Если мы продолжим предыдущий пример, мы можем рассмотреть создание демо-пользователя для таблицы User.
Для управления всеми миграциями данных вы можете использовать сиды. Файлы семян — это некоторые изменения в данных, которые можно использовать для заполнения таблицы базы данных образцовыми данными или тестовыми данными.
Давайте создадим файл семени, который добавит демо-пользователя в нашу таблицу 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, {});
}
};
Запуск семян
На последнем шаге вы создали файл семени. Он еще не сохранен в базе данных. Для этого нам нужно выполнить простую команду.
$ node_modules/.bin/sequelize db:seed:all
Это выполнит этот файл семени, и у вас будет демон-пользователь, добавленный в таблицу User.
Примечание: Выполнение семян не сохраняется нигде, в отличие от миграций, которые используют таблицу SequelizeMeta. Если вы хотите переопределить это, пожалуйста, прочитайте раздел Storage
Отмена семян
Семена могут быть отменены, если они используют какое-либо хранилище. Для этого доступны две команды:
Если вы хотите отменить последнее семя
node_modules/.bin/sequelize db:seed:undo
Если вы хотите отменить все семена
node_modules/.bin/sequelize db:seed:undo:all
Дополнительные темы
Шаблон миграции
Следующий шаблон показывает типичный файл миграции.
module.exports = {
up: (queryInterface, Sequelize) => {
// logic for transforming into the new state
},
down: (queryInterface, Sequelize) => {
// logic for reverting the changes
}
}
Переданный 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');
}
}
Файл .sequelizerc
Это специальный файл конфигурации. Он позволяет задавать различные параметры, которые обычно передаются в качестве аргументов в CLI. Некоторые сценарии, где вы можете использовать его:
- Вы хотите переопределить путь по умолчанию к папке
migrations,models,seedersилиconfig. - Вы хотите переименовать
config.jsonв что-то другое, например,database.json
И многое другое. Давайте посмотрим, как вы можете использовать этот файл для настройки.
Для начала создадим пустой файл в корневой папке вашего проекта.
$ 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в качестве папки миграций
Динамическая конфигурация
Файл конфигурации по умолчанию — это JSON-файл с именем config.json . Но иногда вам нужно выполнить код или получить доступ к переменным среды, что невозможно в JSON-файлах.
Sequelize CLI может считывать как из файла JSON, так и из файла JS. Это можно настроить с помощью файла .sequelizerc . Давайте посмотрим, как это сделать
Сначала вам нужно создать файл .sequelizerc в корневой папке вашего проекта. Этот файл должен переопределить путь к конфигурации на файл JS. Вот так
const path = require('path');
module.exports = {
'config': path.resolve('config', 'config.js')
}
Теперь Sequelize CLI загрузит config/config.js для получения параметров конфигурации. Поскольку это JS-файл, вы можете выполнить любой код и экспортировать конечный файл динамической конфигурации.
Пример файла config/config.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')
}
}
}
};
Использование переменных среды
С помощью CLI вы можете напрямую получить доступ к переменным среды внутри файла config/config.js . Вы можете использовать .sequelizerc , чтобы сообщить 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'
}
Указание параметров диалекта
Иногда вам нужно указать параметр диалекта. Если это общая конфигурация, вы можете просто добавить его в config/config.json. Иногда вы хотите выполнить код для получения параметров диалекта, в таких случаях вы должны использовать динамический файл конфигурации.
{
"production": {
"dialect":"mysql",
"dialectOptions": {
"bigNumberStrings": true
}
}
}
Использование в производственной среде
Некоторые советы по использованию 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')
}
}
}
};
Наша цель — использовать переменные среды для различных секретов базы данных и не случайно включать их в систему управления версиями.
Хранилище
Существует три типа хранилищ, которые вы можете использовать: sequelize, json, и none.
-
sequelize: сохраняет миграции и семена в таблице в базе данных sequelize -
json: сохраняет миграции и семена в JSON-файле -
none: не сохраняет никакие миграции/семена
Хранилище миграций
По умолчанию CLI создаст таблицу в вашей базе данных под названием SequelizeMeta , содержащую запись для каждой выполненной миграции. Чтобы изменить это поведение, вы можете добавить три параметра в файл конфигурации. Используя migrationStorage, вы можете выбрать тип хранилища, используемого для миграций. Если вы выберите json, вы можете указать путь к файлу с помощью migrationStoragePath, или CLI запишет в файл sequelize-meta.json. Если вы хотите сохранить информацию в базе данных, используя sequelize, но хотите использовать другую таблицу, вы можете изменить имя таблицы с помощью migrationStorageTableName.
{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql",
// Use a different storage type. Default: sequelize
"migrationStorage": "json",
// Use a different file name. Default: sequelize-meta.json
"migrationStoragePath": "sequelizeMeta.json",
// Use a different table name. Default: 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"
}
}
Строка подключения конфигурации
В качестве альтернативы варианту --config с файлами конфигурации, определяющими вашу базу данных, можно использовать вариант --url для передачи строки подключения. Например:
$ node_modules/.bin/sequelize db:migrate --url 'mysql://root:password@mysql_host.com/database_name'
Подключение через SSL
Убедитесь, что ssl указан как в dialectOptions, так и в основной конфигурации.
{
"production": {
"dialect":"postgres",
"ssl": true,
"dialectOptions": {
"ssl": true
}
}
}
Программное использование
У Sequelize есть вспомогательная библиотека для программного управления выполнением и протоколированием задач миграции.
Интерфейс запросов
Используя объект queryInterface, описанный ранее, можно изменить схему базы данных. Чтобы увидеть полный список поддерживаемых им публичных методов, обратитесь к API интерфейса запросов.
Обновление до V4
Обновление до V4
Sequelize v4 является текущей версией и вводит некоторые несовместимые изменения. Большая часть кода Sequelize была переработана для использования возможностей ES2015. В данном руководстве перечислены некоторые изменения для обновления с версии v3 до v4.
Журнал изменений
Полный Журнал изменений для релиза v4.
Несовместимые изменения
Node
Для использования новых возможностей ES2015 Sequelize v4 требует как минимум Node v4 или выше.
Общие
- Плагин Counter Cache и, как следствие, опция
counterCacheдля ассоциаций были удалены. - Диалект MariaDB теперь удалён. Это был просто тонкий обертка над MySQL. Вы можете установить
dialect: 'mysql', и Sequelize должен работать с сервером MariaDB. -
Model.Instanceиinstance.Modelудалены. Для доступа к модели из экземпляра просто используйтеinstance.constructor. Класс Instance (Model.Instance) теперь является самой моделью. - Sequelize теперь использует независимую копию библиотеки bluebird.
- Обещания, возвращаемые sequelize, теперь являются экземплярами
Sequelize.Promiseвместо глобального bluebirdPromise. - Библиотека пулинга была обновлена до
v3, теперь вам необходимо вызватьsequelize.close()для завершения работы пула.
Конфигурация / Параметры
-
Удалена поддержка старых ключей конфигурации пулинга подключений. Вместо
Старое
pool: { maxIdleTime: 30000, minConnections: 20, maxConnections: 30 }Новое
pool: { idle: 30000, min: 20, max: 30 } - Удалена поддержка
pool: false. Для использования одного подключения установитеpool.maxв 1. - Удалена поддержка
referencesKey, используйте объект referencesreferences: { key: '', model: '' } -
Удалённые опции
classMethodsиinstanceMethodsизsequelize.define. Модели Sequelize теперь являются классами ES6. Вы можете устанавливать методы на уровне класса/экземпляра следующим образом:Старое
const Model = sequelize.define('Model', { ... }, { classMethods: { associate: function (model) {...} }, instanceMethods: { someMethod: function () { ...} } });Новое
const Model = sequelize.define('Model', { ... }); // Class Method Model.associate = function (models) { ...associate the models }; // Instance Method Model.prototype.someMethod = function () {..} -
options.orderтеперь принимает только значения с типом массива или метода Sequelize. Поддержка строковых значений (например,{order: 'name DESC'}) устарела. - Для отношений
BelongsToManyустановщикиadd/set/createтеперь устанавливаются через атрибуты, передавая их какoptions.through(ранее второй аргумент использовался как атрибуты через, теперь он рассматривается как параметры сthroughв качестве подпараметра) -
Сырые параметры для where, order и group, такие как
where: { $raw: '..', order: [{ raw: '..' }], group: [{ raw: '..' }] }, были удалены для предотвращения атак SQL-инъекций.Старое
user.addProject(project, { status: 'started' });Новое
user.addProject(project, { through: { status: 'started' } });
Типы данных
- (MySQL/Postgres)
BIGINTтеперь возвращается как строка. - (MySQL/Postgres)
DECIMALиNEWDECIMALтипы теперь возвращаются как строка. - (MSSQL)
DataTypes.DATEтеперь используетDATETIMEOFFSETвместоDATETIME2sql типа данных в случае MSSQL для записи часового пояса. Чтобы мигрировать существующие столбцыDATETIME2вDATETIMEOFFSET, см. #7201. -
DATEONLYтеперь возвращает строку в форматеYYYY-MM-DDвместо типаDate
Транзакции/CLS
- Удален
autocommit: trueпо умолчанию, установите этот параметр явно, чтобы транзакции автоматически завершались. - Удален уровень изоляции транзакции
REPEATABLE_READпо умолчанию. Уровень изоляции теперь по умолчанию соответствует уровню изоляции базы данных. Явно укажите необходимый уровень изоляции при инициализации транзакции. -
Исправление CLS не затрагивает глобальное обещание bluebird. Транзакция не будет автоматически передаваться методам при использовании с
Promise.allи другими методами bluebird. Явно исправьте свой экземпляр bluebird, чтобы CLS работал с методами bluebird.$ npm install --save cls-bluebirdconst Sequelize = require('sequelize'); const Promise = require('bluebird'); const clsBluebird = require('cls-bluebird'); const cls = require('continuation-local-storage'); const ns = cls.createNamespace('transaction-namespace'); clsBluebird(ns, Promise); Sequelize.useCLS(ns);
Сырые запросы
- Sequelize теперь поддерживает параметры связи для всех диалектов. В v3 опция
bindпо умолчанию использовала быreplacements, если диалект не поддерживал привязку. Это может быть несовместимым изменением для MySQL/MSSQL, где теперь запросы фактически будут использовать параметры привязки вместо замены по умолчанию.
Другие
-
Sequelize.Validatorтеперь представляет собой независимую копию библиотекиvalidator. -
Метод экземпляра
Model.validateтеперь по умолчанию выполняет хуки валидации. Раньше вам нужно было передавать{ hooks: true }. Вы можете изменить это поведение, передав{ hooks: false }. - Возвращаемое обещание метода экземпляра
Model.validateбудет отклонено при ошибке валидации. Оно будет выполнено при успешной валидации. -
Sequelize.Utilsбольше не является частью публичного API, используйте его на свой страх и риск. -
Hooksтеперь должно возвращать обещания. Обратные вызовы устарели. - Геттеры не будут запускаться с
instance.get({ raw: true }), используйтеinstance.get({ plain: true }) -
requiredвнутри include не распространяется вверх по цепочке include.Чтобы получить результаты, совместимые с v3, вам нужно либо установить
requiredв содержащем include.Старое
user.findOne({ include: { model: project, include: { model: task, required: true } } });Новое
User.findOne({ include: { model: Project, required: true, include: { model: Task, required: true } } }); User.findOne({ include: { model: Project, required: true, include: { model: Task, where: { type: 'important' } //where cause required to default to true } } });Вы можете добавить хук
beforeFind, чтобы получить поведение, совместимое с v3 -function propagateRequired(modelDescriptor) { let include = modelDescriptor.include; if (!include) return false; if (!Array.isArray(include)) include = [include]; return include.reduce((isRequired, descriptor) => { const hasRequiredChild = propogateRequired(descriptor); if ((descriptor.where || hasRequiredChild) && descriptor.required === undefined) { descriptor.required = true; } return descriptor.required || isRequired; }, false); } const sequelize = new Sequelize(..., { ..., define: { hooks: { beforeFind: propagateRequired } } });
Работа с устаревшими таблицами
Работа с устаревшими таблицами
Хотя Sequelize из коробки может показаться несколько предвзятым, легко сделать ваше приложение как совместимым со старыми данными, так и с новыми, определив (в противном случае сгенерированные) имена таблиц и полей.
Таблицы
sequelize.define('user', {
}, {
tableName: 'users'
});
Поля
sequelize.define('modelName', {
userId: {
type: Sequelize.INTEGER,
field: 'user_id'
}
});
Первичные ключи
Sequelize по умолчанию предполагает, что ваша таблица имеет свойство первичного ключа id.
Чтобы определить свой собственный первичный ключ:
sequelize.define('collection', {
uid: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true // Automatically gets converted to SERIAL for postgres
}
});
sequelize.define('collection', {
uuid: {
type: Sequelize.UUID,
primaryKey: true
}
});
И если в вашей модели вообще нет первичного ключа, вы можете использовать Model.removeAttribute('id');
Внешние ключи
// 1:1
Organization.belongsTo(User, {foreignKey: 'owner_id'});
User.hasOne(Organization, {foreignKey: 'owner_id'});
// 1:M
Project.hasMany(Task, {foreignKey: 'tasks_pk'});
Task.belongsTo(Project, {foreignKey: 'tasks_pk'});
// N:M
User.hasMany(Role, {through: 'user_has_roles', foreignKey: 'user_role_user_id'});
Role.hasMany(User, {through: 'user_has_roles', foreignKey: 'roles_identifier'});
Ссылки
Ссылки
Краткое описание классов
| Краткое описание статических публичных классов | ||
|---|---|---|
| public | Выбрасывается, когда подключение к базе данных отклоняется из-за недостаточных привилегий. | |
| public | Создание связей в sequelize выполняется путем вызова одной из функций belongsTo / hasOne / hasMany / belongsToMany на модели (источник) и предоставления другой модели в качестве первого аргумента функции (цель). | |
| public | Выбрасывается, когда связь построена неправильно (подробности см. в сообщении об ошибке). | |
| public | Sequelize предоставляет множество пользовательских классов ошибок, чтобы упростить отладку. | |
| public | Связь один-к-одному | |
| public | Связь многие-ко-многим с таблицей соединения. | |
| public | Ошибка при обработке нескольких записей(error: Ошибка, record: Объект) Выбрасывается, когда операция с несколькими записями терпит неудачу; представляет ошибку на уровне каждой записи. | |
| public | Базовый класс для всех ошибок, связанных с подключением. | |
| public | Выбрасывается, когда подключение к базе данных отклоняется. | |
| public | Выбрасывается, когда подключение к базе данных истекает. | |
| public | Базовый класс для всех ошибок, связанных с базой данных. | |
| public | Выбрасывается, когда оператор include построен неправильно (подробности см. в сообщении об ошибке). | |
| public | Выбрасывается, когда запись не найдена. Обычно используется с режимом rejectOnEmpty (подробности см. в сообщении об ошибке). | |
| public | Выбрасывается, когда нарушается ограничение исключения в базе данных. | |
| public | Выбрасывается, когда нарушается ограничение внешнего ключа в базе данных. | |
| public | Связь один-ко-многим | |
| public | Связь один-к-одному | |
| public | Выбрасывается, когда имя хоста для подключения к базе данных не найдено. | |
| public | Выбрасывается, когда хост для подключения к базе данных недоступен. | |
| public | Выбрасывается при возникновении проблемы с методами экземпляра (подробности см. в сообщении об ошибке). | |
| public | Выбрасывается, когда подключение к базе данных имеет недействительные значения для параметров подключения. | |
| public | Модель представляет таблицу в базе данных. | |
| public | Выбрасывается при попытке обновить устаревшую модель. | |
| public | Выбрасывается, когда в запрос переданы недействительные параметры (подробности см. в сообщении об ошибке). | |
| public | Интерфейс, который Sequelize использует для взаимодействия со всеми базами данных. | |
| public | Основной класс, точка входа в Sequelize. | |
| public | Ошибка области применения. | |
| public | Выбрасывается, когда запрос к базе данных истекает из-за тупика. | |
| public | Объект транзакции используется для идентификации активной транзакции. | |
| public | Выбрасывается, когда нарушается ограничение уникальности в базе данных. | |
| public | Выбрасывается, когда имя ограничения не найдено в базе данных | |
| public | ОшибкаВалидации(message: string, errors: Array) Ошибка валидации. | |
| public | ЭлементОшибкиВалидации(message: String, type: String, path: String, value: String, inst: Object, validatorKey: Object, fnName: String, fnArgs: String) Экземпляры этого класса включены в свойство |
Краткое описание функций
| Краткое описание статических публичных функций | ||
|---|---|---|
| public | isImmutable(value: *, validatorArgs: *, field: *, modelInstance: *): * Валидаторы, основанные на экземплярах | |
Краткое описание переменных
| Краткое описание статических публичных переменных | ||
|---|---|---|
| public | DataTypes: * Удобный класс, содержащий часто используемые типы данных. | |
| public | Deferrable: * Коллекция свойств, относящихся к отложенным ограничениям. | |
| public | Op: {"eq": *, "ne": *, "gte": *, "gt": *, "lte": *, "lt": *, "not": *, "is": *, "in": *, "notIn": *, "like": *, "notLike": *, "iLike": *, "notILike": *, "regexp": *, "notRegexp": *, "iRegexp": *, "notIRegexp": *, "between": *, "notBetween": *, "overlap": *, "contains": *, "contained": *, "adjacent": *, "strictLeft": *, "strictRight": *, "noExtendRight": *, "noExtendLeft": *, "and": *, "or": *, "any": *, "all": *, "values": *, "col": *, "placeholder": *, "join": *, "raw": *} Символы операторов, используемые при запросе данных | |
| public | ТипыЗапросов: * Перечисление типов запросов, используемых | |
| public | Перечисление подсказок для таблиц, используемых в mssql для запросов с подсказками для таблиц | |
Кто использует Sequelize?
Кто использует Sequelize?
... мы активные пользователи Sequelize (и уже 18 месяцев) (Февраль 2017)
Мы используем Sequelize с момента запуска в начале 2015 года. Мы используем его для наших серверов GraphQL (в связке с graphql-sequelize) и для всех наших фоновых задач.
Мы использовали Sequelize в корпоративных проектах для некоторых наших клиентов из списка Fortune 100 и Fortune 500. Он используется в развертываниях, от которых ежегодно зависят сотни миллионов устройств.
Используем Sequelize в производстве для двух разных приложений с более чем 30 000 ежедневных пользователей уже 2 года. Я сомневаюсь, что в настоящее время есть что-то лучшее в плане производительности и функциональности.
Отказ от ответственности
Отказ от ответственности
- Это скучная юридическая информация для остальных. Поскольку есть люди, которые подают в суд просто так, вы можете найти соответствующую информацию об авторе страницы прямо здесь. Приятного чтения...
АВТОР(Ы)
Main author:
Sascha Depold
Uhlandstr. 160
10719 Berlin
sascha [at] depold [dot] com
[plus] 49 152 [slash] 03878582
ОТВЕТСТВЕННОСТЬ ЗА СОДЕРЖАНИЕ
Ich übernehme keine Haftung für ausgehende Links.
Daher musst du dich bei Problemen an deren Betreiber wenden!
Copyright © 2014–present Sequelize contributors
Licensed under the MIT License.
https://sequelize.org/v4/manual/index.html