Если это детская обувь, которая часто использует Node для разработки на стороне сервера, неизбежно работать с базой данных и выполнять некоторые добавления, удаления, изменения и проверки (CRUD
,Create Read Update Delete
), если это какая-то простая операция, похожая на сценарий синхронизации или что-то в этом роде, вы можете напрямую написать операторы SQL для реализации функции, и если это в некоторых крупных проектах, десятках или сотнях таблиц, между ними также будет несколько (один-ко-многим, многие-ко-многим) отношения отображения, затем введитеORM
(Object Relational Mapping
) инструменты, помогающие нам работать с базой данных, могут уменьшить часть ненужной рабочей нагрузки,Sequelize
Это один из самых популярных.
Ручная склейка SQL исходной версии CRUD
Давайте возьмем пример, чтобы проиллюстрировать прямое соединениеSQL
Оператор сравнивает «низкоуровневую» операцию следующим образом:
CREATE TABLE animal (
id INT AUTO_INCREMENT,
name VARCHAR(14) NOT NULL,
weight INT NOT NULL,
PRIMARY KEY (`id`)
);
Создайте такую таблицу, три поля, идентификатор автоинкремента,name
а такжеweight
.
При использованииmysql
Этот пакет для прямого управления базой данных, вероятно, выглядит так:
const connection = mysql.createConnection({})
const tableName = 'animal'
connection.connect()
// 我们假设已经支持了Promise
// 查询
const [results] = await connection.query(`
SELECT
id,
name,
weight
FROM ${tableName}
`)
// 新增
const name = 'Niko'
const weight = 70
await connection.query(`
INSERT INTO ${tableName} (name, weight)
VALUES ('${name}', ${weight})
`)
// 或者通过传入一个Object的方式也可以做到
await connection.query(`INSERT INTO ${tableName} SET ?`, {
name,
weight
})
connection.end()
Это кажется относительно ясным, но проблема заключается в том, что разработчикам необходимо достаточно знать структуру таблицы.
Если в таблице больше десятка полей, это будет большой расход памяти для разработчиков, нужно знать, что такое поле, склейкаSQL
При вставке обратите внимание на порядок и тип вставки.WHERE
Тип параметра запроса, соответствующий условию. При изменении типа поля необходимо обработать соответствующий параметр.
Такой проект еще страшнее, особенно при сдаче, а новичкам приходится изучать эти табличные структуры с нуля.
И есть еще одна проблема, если вам когда-нибудь понадобится заменить базу данных, откажитесьMySQL
, тогда всеSQL
Операторы должны быть изменены (поскольку диалект каждой базы данных может отличаться)
Использование CRUD расширенной версии Sequelize
Что касается памяти, машина определенно будет надежнее человеческого мозга, так что естьORM
, который используется здесь вNode
тем популярнееSequelize
.
что такое ОРМ
Во-первых, может быть необходимо объяснитьORM
Что он делает?Это можно просто понять как использование объектно-ориентированного подхода для связи с базой данных путем манипулирования объектами.CRUD
Действия.
Разработчикам не нужно заботиться ни о типе базы данных, ни о фактической структуре таблиц, а сопоставлять структуру объектов на текущем языке программирования с таблицами и полями в базе данных.
что касается вышеизложенногоanimal
Работа с таблицей, больше не нужно склеивать кодSQL
заявление, но напрямую вызовите что-то вродеAnimal.create
,Animal.find
Соответствующее действие может быть выполнено.
Как использовать секвенсор
Сначала нам нужно скачатьSequelize
Зависимости:
npm i sequelize
npm i mysql2 # 以及对应的我们需要的数据库驱动
Затем в программе создайтеSequelize
Пример:
const Sequelize = require('Sequelize')
const sequelize = new Sequelize('mysql://root:jarvis@127.0.0.1:3306/ts_test')
// dialect://username:password@host:port/db_name
// 针对上述的表,我们需要先建立对应的模型:
const Animal = sequelize.define('animal', {
id: { type: Sequelize.INTEGER, autoIncrement: true },
name: { type: Sequelize.STRING, allowNull: false },
weight: { type: Sequelize.INTEGER, allowNull: false },
}, {
// 禁止sequelize修改表名,默认会在animal后边添加一个字母`s`表示负数
freezeTableName: true,
// 禁止自动添加时间戳相关属性
timestamps: false,
})
// 然后就可以开始使用咯
// 还是假设方法都已经支持了Promise
// 查询
const results = await Animal.findAll({
raw: true,
})
// 新增
const name = 'Niko'
const weight = 70
await Animal.create({
name,
weight,
})
sequenceize определяет различные конфигурации, связанные с моделью:docs
Опуская часть определения модели, используйтеSequelize
Несомненно, стоимость использования лота снижается, потому что определение модели, как правило, не меняется, и определение используется несколько раз за один раз, и используется ручное сращивание.SQL
способ может потребоватьSQL
Менялся и менялся.
И это может помочь преобразовать типы полей, чтобы избежать ошибок приведения типов.NaN
Или некоторые ошибки по невнимательности, такие как усеченные числа.
Определяя модель, мы сообщаем программе, что такое модели и каковы поля модели, и позволяем программе помочь нам запомнить, а не позволять запоминать ее самим.
Нам просто нужно, чтобы соответствующая модель работала.
этого не достаточно
But, хотя переход наORM
Инструмент помог нам сократить большую часть затрат на память, но этого все еще недостаточно. Нам все еще нужно знать, какие поля есть в модели, прежде чем мы сможем использовать ее в бизнес-логике. Если проект возьмет на себя новый человек, нам все еще нужно взглянуть на определение модели, Только тогда мы сможем узнать, какие поля есть, поэтому сегодня мы поговорим о настоящем главном герое:sequelize-typescript
CRUD Ultimate Decorator реализует определение модели
Sequelize-typescript
основан наSequelize
противTypeScript
Реализована расширенная версия, которая отказывается от предыдущего громоздкого определения модели и использует декораторы для непосредственного достижения цели, о которой мы думаем.
Как используется Sequelize-typescript
Во-первых, потому что он используетсяTS
, поэтому будет больше вещей, которые нужно установить в зависимостях среды:
# 这里采用ts-node来完成举例
npm i ts-node typescript
npm i sequelize reflect-metadata sequelize-typescript
Во-вторых, необходимо изменитьTS
соответствующий проектуtsconfig.json
файл, чтобы позволитьTS
Поддерживается использование декораторов:
{
"compilerOptions": {
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
}
}
Затем вы можете начать писать сценарии для разработки, сSequelize
Разница в основном в том, где определяется модель:
// /modles/animal.ts
import { Table, Column, Model } from 'sequelize-typescript'
@Table({
tableName: 'animal'
})
export class Animal extends Model<Animal> {
@Column({
primaryKey: true,
autoIncrement: true,
})
id: number
@Column
name: string
@Column
weight: number
}
// 创建与数据库的链接、初始化模型
// app.ts
import path from 'path'
import { Sequelize } from 'sequelize-typescript'
import Animal from './models/animal'
const sequelize = new Sequelize('mysql://root:jarvis@127.0.0.1:3306/ts_test')
sequelize.addModels([path.resolve(__dirname, `./models/`)])
// 查询
const results = await Animal.findAll({
raw: true,
})
// 新增
const name = 'Niko'
const weight = 70
await Animal.create({
name,
weight,
})
с обычнымSequelize
Различия заключаются в следующем:
- Определение модели определяется в виде декоратора
- создавать экземпляр
Sequelize
Когда объект должен указать соответствующийmodel
дорожка - Поддерживается ряд методов, связанных с моделью
Promise
из
Если вы столкнулись с подсказкой во время использованияXXX used before model init
, вы можете попробовать добавитьawait
оператора, дождитесь установления соединения с базой данных перед выполнением операции
Но кажется, что код, написанный таким образом, сравнивается сSequelize
Их намного больше, и для совместной работы нужно как минимум два файла, так какой смысл это делать?
ответOOP
Важная идея в:наследовать.
Наследование модели с использованием Sequelize-typescript
так какTypeScript
основных разработчиков включаютC#
архитектор, такTypeScript
Есть много подобныхC#
Трассировки. В этом аспекте модели мы можем попытаться использовать наследование, чтобы сократить часть избыточного кода.
Например, мы основываемся наanimal
В таблице есть две новые таблицы,dog
иbird
, между ними определенно есть разница, поэтому есть такое определение:
CREATE TABLE dog (
id INT AUTO_INCREMENT,
name VARCHAR(14) NOT NULL,
weight INT NOT NULL,
leg INT NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE bird (
id INT AUTO_INCREMENT,
name VARCHAR(14) NOT NULL,
weight INT NOT NULL,
wing INT NOT NULL,
claw INT NOT NULL,
PRIMARY KEY (`id`)
);
оdog
у нас есть ногаleg
Описание количества, оbird
у нас есть крыльяwing
и лапыclaw
Описание количества.
Количество специальных полей у них намеренно разное, и у провинции есть возможность сказать, что его можно добавить, добавивtype
Поле различает двух разных животных :p
Если вы хотите использоватьSequelize
образом, мы собираемся определить некоторые из тех же полейdefine
Это может быть достигнуто только три раза, или это может быть записано более гибко.define
используется, когдаObject
вытащить, чтобы использоватьObject.assign
способ добиться эффекта, аналогичного наследованию.
Но когдаSequelize-typescript
Мы можем напрямую использовать наследование для достижения желаемого эффекта:
// 首先还是我们的Animal模型定义
// /models/animal.ts
import { Table, Column, Model } from 'sequelize-typescript'
@Table({
tableName: 'animal'
})
export default class Animal extends Model<Animal> {
@Column({
primaryKey: true,
autoIncrement: true,
})
id: number
@Column
name: string
@Column
weight: number
}
// 接下来就是继承的使用了
// /models/dog.ts
import { Table, Column, Model } from 'sequelize-typescript'
import Animal from './animal'
@Table({
tableName: 'dog'
})
export default class Dog extends Animal {
@Column
leg: number
}
// /models/bird.ts
import { Table, Column, Model } from 'sequelize-typescript'
import Animal from './animal'
@Table({
tableName: 'bird'
})
export default class Bird extends Animal {
@Column
wing: number
@Column
claw: number
}
Одно замечание:Каждая модель должна занимать отдельный файл, и использоватьexport default
способ экспорта
То есть наша текущая файловая структура выглядит так:
├── models
│ ├── animal.ts
│ ├── bird.ts
│ └── dog.ts
└── app.ts
выгода отTypeScript
Статический тип, мы можем легко узнать взаимосвязь между этими моделями и какие поля существуют.
в комбинацииVS Code
Вы можете получить много динамических подсказок во время разработки, похожих наfindAll
,create
Будут подсказки для таких операций, как:
Animal.create<Animal>({
abc: 1,
// ^ abc不是Animal已知的属性
})
Повторное использование некоторого поведения через наследование
В приведенном выше примере показано только повторное использование модели, но что, если это какой-то инкапсулированный метод?
Аналогично получить все данные в таблице, возможно получить вообщеJSON
Данных достаточно, т.findAll({raw: true})
Таким образом, мы можем сделать простую инкапсуляцию для подобных операций без необходимости разработчикам вручную вызыватьfindAll
:
// /models/animal.ts
import { Table, Column, Model } from 'sequelize-typescript'
@Table({
tableName: 'animal'
})
export default class Animal extends Model<Animal> {
@Column({
primaryKey: true,
autoIncrement: true,
})
id: number
@Column
name: string
@Column
weight: number
static async getList () {
return this.findAll({raw: true})
}
}
// /app.ts
// 这样就可以直接调用`getList`来实现类似的效果了
await Animal.getList() // 返回一个JSON数组
Точно так же, поскольку наши два вышеDog
иBird
унаследовано отAnimal
, поэтому код можно использовать напрямую, не меняяgetList
.
const results = await Dog.getList()
results[0].leg // TS提示错误
Но если вы используете его, как указано выше, TS выдаст ошибку:[ts] 类型“Animal”上不存在属性“leg”。
.
Ха-ха, почему это? Внимательные студенты могут обнаружить, чтоgetList
Возвращаемое значение представляет собойAnimal[]
типа, так нет вышеleg
Атрибуты,Bird
То же верно и для двух свойств .
Так что нам нужно учитьTS
Знайте нашу структуру данных, поэтому нам нужно ориентироватьсяAnimal
Определение было изменено для использованияДженерики.
Мы добавляем универсальное определение в функцию и добавляем ограничения, чтобы гарантировать, что входящий универсальный тип должен быть унаследован отAnimal
, который преобразует возвращаемое значение в его типT
, функция может быть реализована.
class Animal {
static async getList<T extends Animal>() {
const results = await this.findAll({
raw: true,
})
return results as T[]
}
}
const dogList = await Dog.getList<Dog>()
// 或者不作任何修改,直接在外边手动as也可以实现类似的效果
// 但是这样还是不太灵活,因为你要预先知道返回值的具体类型结构,将预期类型传递给函数,由函数去组装返回的类型还是比较推荐的
const dogList = await Dog.getList() as Dog[]
console.log(dogList[0].leg) // success
затем используйтеleg
Свойства не ошибутся.Если вы хотите использовать дженерики, вы должны не забыть добавитьextends Animal
ограничения, иначеTS
Будет считаться, что сюда можно передать любой тип, поэтому сложно обеспечить корректную совместимость.Animal
, но унаследовано отAnimal
должны быть совместимы.
Конечно, если даже парадигма здесь илиas
Если вы не хотите его писать, вы также можете переопределить метод родительского класса в подклассе.
Нет необходимости в полной логике реализации, просто получите возвращаемое значение и измените его на нужный нам тип:
class Dog extends Animal {
static async getList() {
// 调用父类方法,然后将返回值指定为某个类型
const results = await super.getList()
return results as Dog[]
}
}
// 这样就可以直接使用方法,而不用担心返回值类型了
const dogList = await Dog.getList()
console.log(dogList[0].leg) // success
резюме
Эта статья является лишь введением, несколько простых примеров, просто чтобы отразить три (SQL
,Sequelize
иSequelize-typescript
)разница между,Sequelize
Есть более высокоуровневые операции, такие как сопоставление отношений, которые находятся вSequelize-typescript
Соответствующие проявления есть в , а из-за использования декораторов кода, необходимого для реализации этих функций, будет намного меньше, и выглядеть он будет понятнее.
Конечно,ORM
Такого рода вещи не говорят о том, что нужно делать все сразу.Если это новичок, я не рекомендую использовать его на личном уровне, потому что это уменьшит вероятность контакта с SQL.
Если структура проекта не слишком сложна или обозримое будущее не слишком сложно, то используйтеORM
Это не имеет особого смысла и усложняет структуру проекта.
И, в определенной степени, универсальный означает компромисс.Для обеспечения одинакового эффекта между несколькими базами данных некоторые уникальные особенности базы данных могут быть отброшены.Если эти функции явно необходимы, тоORM
не слишком подходит
Выберите наиболее подходящий, чтобы узнать смысл использования чего-либо
Последний пример находится на GitHub:notebook | typescript/sequelize
Использованная литература: