Typeorm - это ORM, который может работать в Nodejs, браузере, кордове, PhoneGap, Ionic, React Native, NativeScript, Expo и электронных платформах и может использоваться с TypeScript и JavaScript (ES2021). Его цель состоит в том, чтобы всегда поддерживать последние функции JavaScript и предоставлять дополнительные функции, которые помогут вам разработать любое приложение, которое использует базы данных - от небольших приложений с несколькими таблицами до крупномасштабных корпоративных приложений с несколькими базами данных.
Typeorm поддерживает как активную записи, так и шаблоны Mapper Data, в отличие от всех других JavaScript Orms, которые в настоящее время существуют, что означает, что вы можете писать высококачественные, слабо связанные, масштабируемые, поддерживаемые приложения наиболее продуктивным образом.
Typeorm сильно влияет на другие ORM, такие как Hibernate, Dectrine и Ontity Framework.
Объявление: будущее типовма
Мы рады поделиться нашим видением оживленного типов - стратегии, ориентированной на создание стабильной, надежной и устойчивой основы в долгосрочной перспективе. Узнайте, как мы структурируем обслуживание и объединяем выделенные ресурсы, чтобы обеспечить процветание типов на долгие годы.
Прочитайте полное объявление
И еще ...
С типичкой ваши модели выглядят так:
import { Entity , PrimaryGeneratedColumn , Column } from "typeorm"
@ Entity ( )
export class User {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( )
firstName : string
@ Column ( )
lastName : string
@ Column ( )
age : number
}
И ваша доменная логика выглядит так:
const userRepository = MyDataSource . getRepository ( User )
const user = new User ( )
user . firstName = "Timber"
user . lastName = "Saw"
user . age = 25
await userRepository . save ( user )
const allUsers = await userRepository . find ( )
const firstUser = await userRepository . findOneBy ( {
id : 1 ,
} ) // find by id
const timber = await userRepository . findOneBy ( {
firstName : "Timber" ,
lastName : "Saw" ,
} ) // find by firstName and lastName
await userRepository . remove ( timber )
В качестве альтернативы, если вы предпочитаете использовать реализацию ActiveRecord
, вы также можете использовать ее:
import { Entity , PrimaryGeneratedColumn , Column , BaseEntity } from "typeorm"
@ Entity ( )
export class User extends BaseEntity {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( )
firstName : string
@ Column ( )
lastName : string
@ Column ( )
age : number
}
И ваша доменная логика будет выглядеть таким образом:
const user = new User ( )
user . firstName = "Timber"
user . lastName = "Saw"
user . age = 25
await user . save ( )
const allUsers = await User . find ( )
const firstUser = await User . findOneBy ( {
id : 1 ,
} )
const timber = await User . findOneBy ( {
firstName : "Timber" ,
lastName : "Saw"
} )
await timber . remove ( )
Установите пакет NPM:
npm install typeorm --save
Вам нужно установить reflect-metadata
Shim:
npm install reflect-metadata --save
и импортируйте его где -то в глобальном месте вашего приложения (например, в app.ts
):
import "reflect-metadata"
Вам может потребоваться установить типинг узлов:
npm install @types/node --save-dev
Установите драйвер базы данных:
для MySQL или MariaDB
npm install mysql --save
(вы можете установить mysql2
вместо этого)
для Postgresql или Tackroachdb
npm install pg --save
для SQLite
npm install sqlite3 --save
Для Microsoft SQL Server
npm install mssql --save
Для SQL.JS
npm install sql.js --save
для Oracle
npm install oracledb --save
Чтобы водитель Oracle работал, вам нужно следовать инструкциям по установке с их сайта.
Для SAP HANA
npm install @sap/hana-client
npm install hdb-pool
Поддержка SAP HANA стала возможной благодаря спонсорству программного обеспечения Neptune.
Для Google Cloud Spanner
npm install @google-cloud/spanner --save
Предоставьте учетные данные аутентификации для вашего кода приложения, установив переменную среды GOOGLE_APPLICATION_CREDENTIALS
:
# Linux/macOS
export GOOGLE_APPLICATION_CREDENTIALS= " KEY_PATH "
# Windows
set GOOGLE_APPLICATION_CREDENTIALS=KEY_PATH
# Replace KEY_PATH with the path of the JSON file that contains your service account key.
Для использования ганочного газа с эмулятором вы должны установить SPANNER_EMULATOR_HOST
Enviration Variable:
# Linux/macOS
export SPANNER_EMULATOR_HOST=localhost:9010
# Windows
set SPANNER_EMULATOR_HOST=localhost:9010
Для MongoDB (экспериментальный)
npm install mongodb@^5.2.0 --save
Для NativeScript , React-National и Cordova
Проверьте документацию поддерживаемых платформ
Установите только один из них, в зависимости от того, какую базу данных вы используете.
Кроме того, убедитесь, что вы используете TypeScript версию 4.5 или выше, и вы включили следующие настройки в tsconfig.json
:
"emitDecoratorMetadata" : true ,
"experimentalDecorators" : true ,
Вам также может потребоваться включить es6
в разделе lib
опций компилятора или установить es6-shim
из @types
.
Самый быстрый способ начать работу с Typeorm - это использовать свои команды CLI для создания стартового проекта. Quick Start работает только в том случае, если вы используете Typeorm в приложении Nodejs. Если вы используете другие платформы, перейдите к пошаговому руководству.
Чтобы создать новый проект, используя CLI, запустите следующую команду:
npx typeorm init --name MyProject --database postgres
Где name
является именем вашего проекта, а database
- это база данных, которую вы используете. База данных может быть одним из следующих значений: mysql
, mariadb
, postgres
, cockroachdb
, sqlite
, mssql
, sap
, spanner
, oracle
, mongodb
, cordova
, react-native
, expo
, nativescript
.
Эта команда генерирует новый проект в каталоге MyProject
со следующими файлами:
MyProject
├── src // place of your TypeScript code
│ ├── entity // place where your entities (database models) are stored
│ │ └── User.ts // sample entity
│ ├── migration // place where your migrations are stored
│ ├── data-source.ts // data source and all connection configuration
│ └── index.ts // start point of your application
├── .gitignore // standard gitignore file
├── package.json // node module dependencies
├── README.md // simple readme file
└── tsconfig.json // TypeScript compiler options
Вы также можете запустить
typeorm init
в рамках существующего проекта узла, но будьте осторожны - он может переопределить некоторые файлы, которые у вас уже есть.
Следующим шагом является установка новых зависимостей проекта:
cd MyProject
npm install
После того, как у вас установлены все зависимости, отредактируйте файл data-source.ts
и поместите там варианты конфигурации подключения к базе данных:
export const AppDataSource = new DataSource ( {
type : "postgres" ,
host : "localhost" ,
port : 5432 ,
username : "test" ,
password : "test" ,
database : "test" ,
synchronize : true ,
logging : true ,
entities : [ Post , Category ] ,
subscribers : [ ] ,
migrations : [ ] ,
} )
В частности, большую часть времени вам нужно только настраивать host
, username
, password
, database
и, возможно, параметры port
.
После того, как вы закончите с конфигурацией и установлены все модули узлов, вы можете запустить свое приложение:
npm start
Вот и все, ваше приложение должно успешно запускать и вставить нового пользователя в базу данных. Вы можете продолжать работать с этим проектом и интегрировать другие модули, которые вам нужны, и начать создавать больше сущностей.
Вы можете генерировать проект ESM, запустив
npx typeorm init --name MyProject --database postgres --module esm
.
Вы можете генерировать еще более продвинутый проект с Express, установленным путем запуска
npx typeorm init --name MyProject --database mysql --express
Command.
Вы можете сгенерировать файл докера, выполнив
npx typeorm init --name MyProject --database postgres --docker
Command.
Чего вы ожидаете от ORM? Прежде всего, вы ожидаете, что он создаст для вас таблицы базы данных и найдет / вставьте / обновляет / удаляет ваши данные без боли от необходимости писать много ресурсов SQL. Это руководство покажет вам, как настроить Typeorm с нуля, и сделает его то, что вы ожидаете от ORM.
Работа с базой данных начинается с создания таблиц. Как сказать SypeRom для создания таблицы баз данных? Ответ - через модели. Ваши модели в вашем приложении - ваши таблицы базы данных.
Например, у вас есть модель Photo
:
export class Photo {
id : number
name : string
description : string
filename : string
views : number
isPublished : boolean
}
И вы хотите хранить фотографии в своей базе данных. Во -первых, для хранения вещей в базе данных вам нужна таблица базы данных, а таблицы базы данных создаются из ваших моделей. Не все модели, а только те, которые вы определяете как сущности .
Сущность - это ваша модель, украшенная декоратором @Entity
. Таблица базы данных будет создана для таких моделей. Вы работаете с сущностями повсюду в Typeorm. Вы можете загрузить/вставить/обновить/удалить и выполнить другие операции с ними.
Давайте сделаем нашу Photo
модель сущностью:
import { Entity } from "typeorm"
@ Entity ( )
export class Photo {
id : number
name : string
description : string
filename : string
views : number
isPublished : boolean
}
Теперь таблица базы данных будет создана для Photo
Entity, и мы сможем работать с ним в любом месте в нашем приложении. Однако мы создали таблицу базы данных, какая таблица может существовать без столбцов? Давайте создадим несколько столбцов в нашей таблице баз данных.
Чтобы добавить столбцы базы данных, вам просто нужно украсить свойства сущности, которые вы хотите превратить в колонку с декоратором @Column
.
import { Entity , Column } from "typeorm"
@ Entity ( )
export class Photo {
@ Column ( )
id : number
@ Column ( )
name : string
@ Column ( )
description : string
@ Column ( )
filename : string
@ Column ( )
views : number
@ Column ( )
isPublished : boolean
}
Теперь id
, name
, description
, filename
, views
и isPublished
столбцы будут добавлены в таблицу photo
. Типы столбцов в базе данных выводится из используемых вами типов, например, number
например, в integer
, string
в varchar
, boolean
в bool
и т. Д., Но вы можете использовать любой тип столбца, который поддерживает вашу базу данных, явно указав тип столбца в @Column
Decorator.
Мы сгенерировали таблицу базы данных с столбцами, но осталась одна вещь. Каждая таблица базы данных должна иметь столбец с первичным ключом.
Каждая сущность должна иметь хотя бы один столбец первичного ключа. Это требование, и вы не можете избежать этого. Чтобы сделать столбец первичным ключом, вам нужно использовать декоратор @PrimaryColumn
.
import { Entity , Column , PrimaryColumn } from "typeorm"
@ Entity ( )
export class Photo {
@ PrimaryColumn ( )
id : number
@ Column ( )
name : string
@ Column ( )
description : string
@ Column ( )
filename : string
@ Column ( )
views : number
@ Column ( )
isPublished : boolean
}
Теперь, допустим, вы хотите, чтобы ваш столбец идентификатор был автоматически сгенерирован (это известно как столбец Auto-Increment / Sequence / Serial / сгенерированный идентификатор). Для этого вам нужно изменить декоратор @PrimaryColumn
на декоратор @PrimaryGeneratedColumn
:
import { Entity , Column , PrimaryGeneratedColumn } from "typeorm"
@ Entity ( )
export class Photo {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( )
name : string
@ Column ( )
description : string
@ Column ( )
filename : string
@ Column ( )
views : number
@ Column ( )
isPublished : boolean
}
Далее, давайте исправим наши типы данных. По умолчанию строка отображается с типом Varchar (255) -lik (в зависимости от типа базы данных). Число отображается с целочисленным типом (в зависимости от типа базы данных). Мы не хотим, чтобы все наши столбцы были ограниченными варчарами или целыми числами. Давайте настроим правильные типы данных:
import { Entity , Column , PrimaryGeneratedColumn } from "typeorm"
@ Entity ( )
export class Photo {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( {
length : 100 ,
} )
name : string
@ Column ( "text" )
description : string
@ Column ( )
filename : string
@ Column ( "double" )
views : number
@ Column ( )
isPublished : boolean
}
Типы столбцов являются специфичными для базы данных. Вы можете установить любой тип столбца, который поддерживает вашу базу данных. Более подробную информацию о поддерживаемых типах столбцов можно найти здесь.
DataSource
Теперь, когда наша сущность создана, давайте создадим файл index.ts
и настроем наш DataSource
там:
import "reflect-metadata"
import { DataSource } from "typeorm"
import { Photo } from "./entity/Photo"
const AppDataSource = new DataSource ( {
type : "postgres" ,
host : "localhost" ,
port : 5432 ,
username : "root" ,
password : "admin" ,
database : "test" ,
entities : [ Photo ] ,
synchronize : true ,
logging : false ,
} )
// to initialize the initial connection with the database, register all entities
// and "synchronize" database schema, call "initialize()" method of a newly created database
// once in your application bootstrap
AppDataSource . initialize ( )
. then ( ( ) => {
// here you can start to work with your database
} )
. catch ( ( error ) => console . log ( error ) )
В этом примере мы используем Postgres, но вы можете использовать любую другую поддерживаемую базу данных. Чтобы использовать другую базу данных, просто измените type
в параметрах для типа базы данных, который вы используете: mysql
, mariadb
, postgres
, cockroachdb
, sqlite
, mssql
, oracle
, sap
, spanner
, cordova
, nativescript
, react-native
, expo
или mongodb
Полем Также обязательно используйте свой собственный хост, порт, имя пользователя, пароль и настройки базы данных.
Мы добавили нашу фотографию в список объектов для этого источника данных. Каждая сущность, которую вы используете в своем соединении, должна быть указана там.
Установка synchronize
гарантирует, что ваши объекты будут синхронизироваться с базой данных, каждый раз, когда вы запускаете приложение.
Теперь, если вы запустите свой index.ts
, будет инициатировано соединение с базой данных, а таблица базы данных будет создана.
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(100) | |
| description | text | |
| filename | varchar(255) | |
| views | int(11) | |
| isPublished | boolean | |
+-------------+--------------+----------------------------+
Теперь давайте создадим новую фотографию, чтобы сохранить ее в базе данных:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photo = new Photo ( )
photo . name = "Me and Bears"
photo . description = "I am near polar bears"
photo . filename = "photo-with-bears.jpg"
photo . views = 1
photo . isPublished = true
await AppDataSource . manager . save ( photo )
console . log ( "Photo has been saved. Photo id is" , photo . id )
Как только ваша сущность будет сохранена, он получит недавно сгенерированный идентификатор. Метод save
возвращает экземпляр того же объекта, который вы передаете ему. Это не новая копия объекта, он изменяет его «ID» и возвращает его.
Мы только что создали новую фотографию и сохранили ее в базе данных. Мы использовали EntityManager
чтобы сохранить его. Используя менеджер Entity, вы можете манипулировать любым объектом в вашем приложении. Например, давайте загрузим нашу сохраненную сущность:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const savedPhotos = await AppDataSource . manager . find ( Photo )
console . log ( "All photos from the db: " , savedPhotos )
savedPhotos
будет массивом фотографий -объектов с данными, загруженными из базы данных.
Узнайте больше о EntityManager здесь.
Теперь давайте рефактируем наш код и используем Repository
вместо EntityManager
. У каждого объекта есть свой собственный репозиторий, который обрабатывает все операции со своей сущностью. Когда вы много имеете дело с организациями, репозитории удобнее использовать, чем EntityManagers:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photo = new Photo ( )
photo . name = "Me and Bears"
photo . description = "I am near polar bears"
photo . filename = "photo-with-bears.jpg"
photo . views = 1
photo . isPublished = true
const photoRepository = AppDataSource . getRepository ( Photo )
await photoRepository . save ( photo )
console . log ( "Photo has been saved" )
const savedPhotos = await photoRepository . find ( )
console . log ( "All photos from the db: " , savedPhotos )
Узнайте больше о репозитории здесь.
Давайте попробуем больше операций нагрузки, используя хранилище:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource . getRepository ( Photo )
const allPhotos = await photoRepository . find ( )
console . log ( "All photos from the db: " , allPhotos )
const firstPhoto = await photoRepository . findOneBy ( {
id : 1 ,
} )
console . log ( "First photo from the db: " , firstPhoto )
const meAndBearsPhoto = await photoRepository . findOneBy ( {
name : "Me and Bears" ,
} )
console . log ( "Me and Bears photo from the db: " , meAndBearsPhoto )
const allViewedPhotos = await photoRepository . findBy ( { views : 1 } )
console . log ( "All viewed photos: " , allViewedPhotos )
const allPublishedPhotos = await photoRepository . findBy ( { isPublished : true } )
console . log ( "All published photos: " , allPublishedPhotos )
const [ photos , photosCount ] = await photoRepository . findAndCount ( )
console . log ( "All photos: " , photos )
console . log ( "Photos count: " , photosCount )
Теперь давайте загрузим одну фотографию из базы данных, обновим ее и сохраните:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource . getRepository ( Photo )
const photoToUpdate = await photoRepository . findOneBy ( {
id : 1 ,
} )
photoToUpdate . name = "Me, my friends and polar bears"
await photoRepository . save ( photoToUpdate )
Теперь фото с id = 1
будет обновлено в базе данных.
Теперь давайте удалим нашу фотографию из базы данных:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource . getRepository ( Photo )
const photoToRemove = await photoRepository . findOneBy ( {
id : 1 ,
} )
await photoRepository . remove ( photoToRemove )
Теперь фото с id = 1
будет удалено из базы данных.
Давайте создадим отношения один на один с другим классом. Давайте создадим новый класс в PhotoMetadata.ts
. Этот класс Photometadata должен содержать дополнительную метаинформацию нашей фотографии:
import {
Entity ,
Column ,
PrimaryGeneratedColumn ,
OneToOne ,
JoinColumn ,
} from "typeorm"
import { Photo } from "./Photo"
@ Entity ( )
export class PhotoMetadata {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( "int" )
height : number
@ Column ( "int" )
width : number
@ Column ( )
orientation : string
@ Column ( )
compressed : boolean
@ Column ( )
comment : string
@ OneToOne ( ( ) => Photo )
@ JoinColumn ( )
photo : Photo
}
Здесь мы используем новый декоратор под названием @OneToOne
. Это позволяет нам создавать отношения между двумя субъектами. type => Photo
- это функция, которая возвращает класс объекта, с которой мы хотим установить наши отношения. Мы вынуждены использовать функцию, которая возвращает класс, вместо того, чтобы использовать класс напрямую, из -за специфики для языка. Мы также можем написать его как () => Photo
, но мы используем type => Photo
как соглашение для увеличения читаемости кода. Сама переменная типа ничего не содержит.
Мы также добавляем декоратор @JoinColumn
, который указывает на то, что эта сторона отношений будет иметь отношения. Отношения могут быть однонаправленными или двунаправленными. Только одна сторона реляции может владеть. Использование @JoinColumn
Decorator требуется на стороне владельца отношений.
Если вы запустите приложение, вы увидите вновь сгенерированную таблицу, и она содержит столбец с иностранным ключом для соотношения фото:
+-------------+--------------+----------------------------+
| photo_metadata |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| height | int(11) | |
| width | int(11) | |
| comment | varchar(255) | |
| compressed | boolean | |
| orientation | varchar(255) | |
| photoId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Теперь давайте сохраним фотографию и ее метаданные и прикрепим их друг к другу.
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
// create a photo
const photo = new Photo ( )
photo . name = "Me and Bears"
photo . description = "I am near polar bears"
photo . filename = "photo-with-bears.jpg"
photo . views = 1
photo . isPublished = true
// create a photo metadata
const metadata = new PhotoMetadata ( )
metadata . height = 640
metadata . width = 480
metadata . compressed = true
metadata . comment = "cybershoot"
metadata . orientation = "portrait"
metadata . photo = photo // this way we connect them
// get entity repositories
const photoRepository = AppDataSource . getRepository ( Photo )
const metadataRepository = AppDataSource . getRepository ( PhotoMetadata )
// first we should save a photo
await photoRepository . save ( photo )
// photo is saved. Now we need to save a photo metadata
await metadataRepository . save ( metadata )
// done
console . log (
"Metadata is saved, and the relation between metadata and photo is created in the database too" ,
)
Отношения могут быть однонаправленными или двунаправленными. В настоящее время наша связь между Photometadata и Photo является однонаправленным. Владельцем отношения является Photometadata, и фото ничего не знает о Photometadata. Это усложняет доступ к Photometadata со стороны фото. Чтобы решить эту проблему, мы должны добавить обратную связь и установить отношения между Photometadata и фотографией двунаправленной. Давайте изменим наши сущности:
import {
Entity ,
Column ,
PrimaryGeneratedColumn ,
OneToOne ,
JoinColumn ,
} from "typeorm"
import { Photo } from "./Photo"
@ Entity ( )
export class PhotoMetadata {
/* ... other columns */
@ OneToOne ( ( ) => Photo , ( photo ) => photo . metadata )
@ JoinColumn ( )
photo : Photo
}
import { Entity , Column , PrimaryGeneratedColumn , OneToOne } from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
@ Entity ( )
export class Photo {
/* ... other columns */
@ OneToOne ( ( ) => PhotoMetadata , ( photoMetadata ) => photoMetadata . photo )
metadata : PhotoMetadata
}
photo => photo.metadata
- это функция, которая возвращает имя обратной стороны отношения. Здесь мы показываем, что свойство метаданных класса фотографий - это то, где мы храним Photometadata в классе фото. Вместо того, чтобы передавать функцию, которая возвращает свойство фотографии, вы можете просто просто передать струну декоратору @OneToOne
, например "metadata"
. Но мы использовали этот подход типа функции, чтобы упростить рефакторинг.
Обратите внимание, что мы должны использовать декоратор @JoinColumn
только на одной стороне отношения. Какую бы сторону вы поставили этот декоратор, будет собственной стороной отношений. Собственная сторона отношения содержит столбец с внешним ключом в базе данных.
Если вы используете ESM в своем проекте TypeScript, вы должны использовать тип обертки Relation
в свойствах отношения, чтобы избежать проблем с круглой зависимости. Давайте изменим наши сущности:
import {
Entity ,
Column ,
PrimaryGeneratedColumn ,
OneToOne ,
JoinColumn ,
Relation ,
} from "typeorm"
import { Photo } from "./Photo"
@ Entity ( )
export class PhotoMetadata {
/* ... other columns */
@ OneToOne ( ( ) => Photo , ( photo ) => photo . metadata )
@ JoinColumn ( )
photo : Relation < Photo >
}
import {
Entity ,
Column ,
PrimaryGeneratedColumn ,
OneToOne ,
Relation ,
} from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
@ Entity ( )
export class Photo {
/* ... other columns */
@ OneToOne ( ( ) => PhotoMetadata , ( photoMetadata ) => photoMetadata . photo )
metadata : Relation < PhotoMetadata >
}
Теперь давайте загрузим наше фото и его метаданные фото за один запрос. Есть два способа сделать это - используя методы find*
или использование функциональности QueryBuilder
. Давайте сначала используем метод find*
. find*
Методы позволяют указать объект с интерфейсом FindOneOptions
/ FindManyOptions
.
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource . getRepository ( Photo )
const photos = await photoRepository . find ( {
relations : {
metadata : true ,
} ,
} )
Здесь фотографии будут содержать массив фотографий из базы данных, и каждая фотография будет содержать свои метаданные фото. Узнайте больше о вариантах поиска в этой документации.
Использование вариантов находки хорошо и мертвым, но если вам нужен более сложный запрос, вам следует вместо этого использовать QueryBuilder
. QueryBuilder
позволяет использовать более сложные запросы элегантно:
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
import { AppDataSource } from "./index"
const photos = await AppDataSource . getRepository ( Photo )
. createQueryBuilder ( "photo" )
. innerJoinAndSelect ( "photo.metadata" , "metadata" )
. getMany ( )
QueryBuilder
позволяет создавать и выполнять запросы SQL практически любой сложности. Когда вы работаете с QueryBuilder
, подумайте, как будто вы создаете запрос SQL. В этом примере «Фото» и «Метаданные» - это псевдонимы, применяемые к выбранным фотографиям. Вы используете псевдонимы для доступа к столбцам и свойствам выбранных данных.
Мы можем настроить каскадные параметры в наших отношениях, в тех случаях, когда мы хотим, чтобы наш связанный объект был сохранен всякий раз, когда другой объект сохранен. Давайте немного изменим декоратор нашей фотографии @OneToOne
:
export class Photo {
// ... other columns
@ OneToOne ( ( ) => PhotoMetadata , ( metadata ) => metadata . photo , {
cascade : true ,
} )
metadata : PhotoMetadata
}
Использование cascade
позволяет нам не отдельно сохранять фотографии и отдельно сохранять объекты метаданных сейчас. Теперь мы можем просто сохранить объект фото, и объект метаданных будет сохранен автоматически из -за вариантов каскада.
import { AppDataSource } from "./index"
// create photo object
const photo = new Photo ( )
photo . name = "Me and Bears"
photo . description = "I am near polar bears"
photo . filename = "photo-with-bears.jpg"
photo . isPublished = true
// create photo metadata object
const metadata = new PhotoMetadata ( )
metadata . height = 640
metadata . width = 480
metadata . compressed = true
metadata . comment = "cybershoot"
metadata . orientation = "portrait"
photo . metadata = metadata // this way we connect them
// get repository
const photoRepository = AppDataSource . getRepository ( Photo )
// saving a photo also save the metadata
await photoRepository . save ( photo )
console . log ( "Photo is saved, photo metadata is saved too." )
Обратите внимание, что теперь мы устанавливаем свойство metadata
фото, а не на собственность photo
метаданных, как и раньше. cascade
функция работает только в том случае, если вы подключите фотографию к его метаданным со стороны фото. Если вы установите сторону метаданных, метаданные не будут сохранены автоматически.
Давайте создадим отношение много-к-одному/одно-многие. Допустим, у фотографии есть один автор, и у каждого автора может быть много фотографий. Во -первых, давайте создадим класс Author
:
import {
Entity ,
Column ,
PrimaryGeneratedColumn ,
OneToMany ,
JoinColumn ,
} from "typeorm"
import { Photo } from "./Photo"
@ Entity ( )
export class Author {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( )
name : string
@ OneToMany ( ( ) => Photo , ( photo ) => photo . author ) // note: we will create author property in the Photo class below
photos : Photo [ ]
}
Author
содержит обратную сторону отношения. OneToMany
- это всегда обратная сторона отношения, и он не может существовать без ManyToOne
на другой стороне отношения.
Теперь давайте добавим сторону владельца отношения в предприятие Photo:
import { Entity , Column , PrimaryGeneratedColumn , ManyToOne } from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
import { Author } from "./Author"
@ Entity ( )
export class Photo {
/* ... other columns */
@ ManyToOne ( ( ) => Author , ( author ) => author . photos )
author : Author
}
В отношениях со многими к одному / один-один, сторона владельца всегда много-до-один. Это означает, что класс, который использует @ManyToOne
будет хранить идентификатор связанного объекта.
После запуска приложения ORM создаст таблицу author
:
+-------------+--------------+----------------------------+
| author |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
Он также изменит таблицу photo
, добавив новый столбец author
и создав для него внешний ключ:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Давайте создадим много-кокое отношение. Допустим, фото может быть во многих альбомах, и каждый альбом может содержать много фотографий. Давайте создадим класс Album
:
import {
Entity ,
PrimaryGeneratedColumn ,
Column ,
ManyToMany ,
JoinTable ,
} from "typeorm"
@ Entity ( )
export class Album {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( )
name : string
@ ManyToMany ( ( ) => Photo , ( photo ) => photo . albums )
@ JoinTable ( )
photos : Photo [ ]
}
@JoinTable
необходимо указать, что это сторона владельца отношений.
Теперь давайте добавим обратную сторону нашего отношения к классу Photo
:
export class Photo {
// ... other columns
@ ManyToMany ( ( ) => Album , ( album ) => album . photos )
albums : Album [ ]
}
После запуска приложения ORM создаст таблицу соединения album_photos_photo_albums :
+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int(11) | PRIMARY KEY FOREIGN KEY |
| photo_id | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
Не забудьте зарегистрировать класс Album
с вашим соединением в ORM:
const options : DataSourceOptions = {
// ... other options
entities : [ Photo , PhotoMetadata , Author , Album ] ,
}
Теперь давайте вставим альбомы и фотографии в нашу базу данных:
import { AppDataSource } from "./index"
// create a few albums
const album1 = new Album ( )
album1 . name = "Bears"
await AppDataSource . manager . save ( album1 )
const album2 = new Album ( )
album2 . name = "Me"
await AppDataSource . manager . save ( album2 )
// create a few photos
const photo = new Photo ( )
photo . name = "Me and Bears"
photo . description = "I am near polar bears"
photo . filename = "photo-with-bears.jpg"
photo . views = 1
photo . isPublished = true
photo . albums = [ album1 , album2 ]
await AppDataSource . manager . save ( photo )
// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await AppDataSource . getRepository ( Photo ) . findOne ( {
where : {
id : 1 ,
} ,
relations : {
albums : true ,
} ,
} )
loadedPhoto
будет равен:
{
id : 1 ,
name : "Me and Bears" ,
description : "I am near polar bears" ,
filename : "photo-with-bears.jpg" ,
albums : [ {
id : 1 ,
name : "Bears"
} , {
id : 2 ,
name : "Me"
} ]
}
Вы можете использовать QueryBuilder для построения SQL -запросов практически любой сложности. Например, вы можете сделать это:
const photos = await AppDataSource . getRepository ( Photo )
. createQueryBuilder ( "photo" ) // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
. innerJoinAndSelect ( "photo.metadata" , "metadata" )
. leftJoinAndSelect ( "photo.albums" , "album" )
. where ( "photo.isPublished = true" )
. andWhere ( "(photo.name = :photoName OR photo.name = :bearName)" )
. orderBy ( "photo.id" , "DESC" )
. skip ( 5 )
. take ( 10 )
. setParameters ( { photoName : "My" , bearName : "Mishka" } )
. getMany ( )
Этот запрос выбирает все опубликованные фотографии с именами "My" или "Mishka". Он выберет результаты из позиции 5 (смещение страниц) и выберет только 10 результатов (предел страниц). Результат выбора будет заказан ID в порядке убывания. Фото альбомы будут оставлены, и их метаданные будут внутренними.
Вы много используете в вашем приложении. Узнайте больше о QueryBuilder здесь.
Взгляните на образцы в образце для примеров использования.
Есть несколько репозитории, которые вы можете клонировать и начать с:
Есть несколько расширений, которые упрощают работу с Typeorm и интегрируют его с другими модулями:
data-source.ts
после генерации миграций/объектов-Sypeorm-Codebase-Syncrelations
- Typeorm -Relationsrelations
на основе запроса GraphQL-Typeorm-Relations GraphQL Узнайте о вкладе здесь и о том, как настроить вашу среду разработки здесь.
Этот проект существует благодаря всем людям, которые вносят свой вклад:
Открытый исходный код тяжелый и трудоемкий. Если вы хотите инвестировать в будущее Typeorm, вы можете стать спонсором и позволить нашей основной команде тратить больше времени на улучшения Typeorm и новые функции. Станьте спонсором
Станьте спонсором золота и получите премиальную техническую поддержку от наших основных участников. Стать золотым спонсором