跳至主内容区

入门指南

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

TypeORM 是一个可在 Node.js、浏览器、Cordova、Ionic、React Native、NativeScript、Expo 和 Electron 平台运行的 ORM,支持 TypeScript 和 JavaScript (ES2021)。

其目标是始终支持最新的 JavaScript 特性,并提供额外功能帮助您开发各类数据库应用——从仅需少量表的小型应用到需要多数据库支持的大型企业应用。

TypeORM 支持的数据库数量超过任何其他 JS/TS ORM:Google SpannerMicrosoft SqlServerMongoDBMySQL/MariaDBOraclePostgresSAP HANASQLite,同时也支持衍生数据库及多种驱动。

TypeORM 同时支持 Active RecordData Mapper 模式(这是现有 JavaScript ORM 中独有的特性),意味着您能以最高效的方式编写高质量、低耦合、可扩展且易维护的应用。

TypeORM 深受其他 ORM 的影响,例如 HibernateDoctrineEntity Framework

特性

  • 同时支持 DataMapperActiveRecord 模式(可自由选择)。

  • 实体与列定义。

  • 数据库特定的列类型。

  • 实体管理器(Entity Manager)。

  • 仓库及自定义仓库(Repositories)。

  • 清晰的对象关系模型。

  • 关联关系(Relations)。

  • 即时加载与延迟加载关系。

  • 支持单向、双向及自引用关系。

  • 支持多种继承模式。

  • 级联操作。

  • 索引管理。

  • 事务支持。

  • 自动生成的迁移

  • 连接池管理。

  • 数据库复制。

  • 多数据库实例支持。

  • 多数据库类型支持。

  • 跨数据库/跨模式查询。

  • 优雅灵活且强大的查询构建器(QueryBuilder)。

  • 左连接与内连接。

  • 带连接查询的精准分页。

  • 查询缓存。

  • 原始结果流式传输。

  • 日志记录。

  • 监听器与订阅器(钩子)。

  • 支持闭包表模式。

  • 模型内声明或独立配置文件管理。

  • 支持 MySQL/MariaDB/Postgres/CockroachDB/SQLite/Microsoft SQL Server/Oracle/SAP Hana/sql.js。

  • 支持 MongoDB NoSQL 数据库。

  • 可在 Node.js/浏览器/Ionic/Cordova/React Native/NativeScript/Expo/Electron 平台运行。

  • 支持 TypeScript 和 JavaScript。

  • 支持 ESM 和 CommonJS 模块规范。

  • 生成高性能、灵活、简洁且可维护的代码。

  • 遵循所有最佳实践。

  • 命令行工具(CLI)。

以及更多功能...

使用 TypeORM,您的模型如下所示:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column()
firstName: string

@Column()
lastName: string

@Column()
age: number
}

而您的领域逻辑如下所示:

const userRepository = AppDataSource.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()

安装

  1. 安装 npm 包:

    npm install typeorm

  2. 需要安装 reflect-metadata 垫片:

    npm install reflect-metadata

    并在应用程序的全局位置导入(例如在 app.ts 中):

    import "reflect-metadata"

  3. 可能需要安装 Node.js 类型定义:

    npm install @types/node --save-dev

  4. 安装数据库驱动:请参阅各驱动的文档:mongodbmssqlmysql/mariadboraclepostgressapspannersqlite

TypeScript 配置

同时请确保使用 TypeScript 4.5 或更高版本,并在 tsconfig.json 中启用以下设置:

"emitDecoratorMetadata": true,
"experimentalDecorators": true,

快速开始

最快入门方式是使用 TypeORM CLI 命令生成初始项目。此方式仅适用于 Node.js 应用程序,若使用其他平台请转至分步指南

使用 CLI 创建新项目,请运行:

npx typeorm init --name MyProject --database postgres

其中 name 是项目名称,database 是数据库类型(可选值:mysqlmariadbpostgrescockroachdbsqlitemssqlsapspanneroraclemongodbcordovareact-nativeexponativescript)。

该命令将在 MyProject 目录生成包含以下文件的新项目:

MyProject
├── src // place of your TypeScript code
│ ├── entities // place where your entities (database models) are stored
│ │ └── User.ts // sample entity
│ ├── migrations // 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

也可在现有 Node.js 项目运行 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: [],
})

通常您只需配置 hostusernamepassworddatabase 和可选的 port 选项。

完成配置并安装所有依赖后,运行应用程序:

npm start

至此应用程序应成功运行,并向数据库插入新用户。您可以继续完善项目,集成所需模块并创建更多实体。

可通过 npx typeorm init --name MyProject --database postgres --module esm 生成 ESM 项目。

可通过 npx typeorm init --name MyProject --database mysql --express 生成包含 Express 的高级项目。

您可以通过运行 npx typeorm init --name MyProject --database postgres --docker 命令生成 docker-compose 文件。

分步指南

您对 ORM 有哪些期待? 首先,您希望它能自动创建数据库表, 并能查找/插入/更新/删除数据,而无需编写大量难以维护的 SQL 查询。 本指南将展示如何从零开始设置 TypeORM,让它满足您对 ORM 的期望。

创建模型

数据库操作始于创建表。 如何让 TypeORM 创建数据库表? 答案是通过模型(models)。 您在应用中的模型就是数据库表。

例如,您有一个 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 实体创建数据库表,我们可以在应用各处使用它。 表已创建,但没有列的表有何意义? 让我们在数据库表中创建几列。

添加表列

要为实体添加数据库列, 需使用 @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
}

现在 idnamedescriptionfilenameviewsisPublished 列将添加到 photo 表。 数据库列类型根据属性类型自动推断, 例如 number 转为 integerstring 转为 varcharboolean 转为 bool 等。 但您可以通过 @Column 装饰器显式指定数据库支持的任何列类型。

我们已创建带列的表,但还有关键一环: 每张数据库表必须有主键列。

创建主键列

每个实体必须至少有一个主键列。 这是硬性要求,无法规避。 要将列设为主键,需使用 @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
}

创建自动生成列

假设您希望 id 列自动生成(称为自增/序列/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) 类型(取决于数据库)
  • 数字映射为类 integer 类型(取决于数据库) 我们不应将所有列限制为 varchar 或 integer。 让我们设置正确的数据类型:
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
try {
await AppDataSource.initialize()
} catch (error) {
console.log(error)
}

本示例使用 Postgres,但您可以使用任何其他支持的数据库。 要使用其他数据库,请将选项中的 type 更改为您使用的数据库类型: mysqlmariadbpostgrescockroachdbsqlitemssqloraclesapspannercordovanativescriptreact-nativeexpomongodb。 同时请确保使用您自己的主机、端口、用户名、密码和数据库设置。

我们将 Photo 实体添加到此数据源的实体列表中。 连接中使用的每个实体都必须在此列出。

设置 synchronize 可确保每次运行应用程序时,您的实体都会与数据库同步。

运行应用程序

现在如果运行 index.ts,将初始化数据库连接并为照片创建数据库表。

+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(100) | |
| description | text | |
| filename | varchar(255) | |
| views | int | |
| 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)

实体保存后将获得新生成的 id。 save 方法返回您传入的同一对象实例(不是新副本),它会修改对象的 "id" 并返回。

使用实体管理器

我们刚创建新照片并保存到数据库。 使用的是 EntityManager 进行保存。 通过实体管理器,您可以操作应用中的任何实体。 例如加载已保存的实体:

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 将是一个包含从数据库加载数据的 Photo 对象数组。

了解更多关于实体管理器

使用存储库

现在重构代码,改用 Repository 替代 EntityManager。 每个实体都有专属存储库处理其所有操作。 当需要频繁操作实体时,存储库比实体管理器更方便:

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 中创建新类,用于存储照片的额外元信息:

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 在实体间建立一对一关系。 同时添加 @JoinColumn 装饰器表明此端是关系所有者。 关系可以是单向或双向的,但只有一方能作为所有者。 在关系所有者端必须使用 @JoinColumn 装饰器。

运行应用后,您将看到新生成的表,其中包含照片关系的外键列:

+-------------+--------------+----------------------------+
| photo_metadata |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| height | int | |
| width | int | |
| comment | varchar(255) | |
| compressed | boolean | |
| orientation | varchar(255) | |
| photoId | int | 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)

// The 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,而 Photo 对 PhotoMetadata 一无所知。这使得从 Photo 端访问 PhotoMetadata 变得复杂。为了解决这个问题,我们应该添加反向关系,使 PhotoMetadata 与 Photo 之间的关系成为双向的。让我们修改实体:

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 是一个返回关系反向端名称的函数。 这里我们展示了 Photo 类的 metadata 属性是存储 PhotoMetadata 的位置。 您也可以传递字符串(如 "metadata")给 @OneToOne 装饰器,而不使用返回 photo 属性的函数。 但我们采用这种函数类型的方式是为了让重构更简单。

注意:@JoinColumn 装饰器应仅用于关系中的一方。使用此装饰器的一方将成为关系的拥有方(owning side),该方对应的数据库表会包含外键列。

ESM 项目中的关系处理

如果您在 TypeScript 项目中使用 ESM 模块系统,应在关系属性上使用 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,
},
})

执行后,photos 将包含从数据库获取的照片数组,每张照片都会自动填充关联的 photoMetadata 属性。更多查询选项请参阅相关文档

虽然查询选项简单易用,但需要复杂查询时应改用 QueryBuilderQueryBuilder 能优雅地处理高级查询场景:

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 的思维模式。此处的 "photo" 和 "metadata" 是分配给照片表的别名,用于访问所选数据的列和属性。

使用级联自动保存关联对象

我们可以在关系中配置级联选项,实现关联对象随主对象自动保存的效果。修改 Photo 的 @OneToOne 装饰器如下:

export class Photo {
// ... other columns

@OneToOne(() => PhotoMetadata, (metadata) => metadata.photo, {
cascade: true,
})
metadata: PhotoMetadata
}

启用 cascade 后,现在无需分别保存 photo 和 metadata 对象。只需保存 photo 对象,其关联的 metadata 就会因级联选项自动保存。

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.")

请注意,现在我们在 photo 端设置 metadata 属性(而非之前的 metadata 端设置 photo 属性)。cascade 特性仅在从 photo 端建立关联时生效——如果从 metadata 端设置关系,metadata 将不会自动保存。

创建多对一/一对多关系

让我们创建多对一/一对多关系。假设每张照片有一个作者,每个作者可拥有多张照片。首先创建 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 | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+

同时它还会修改 photo 表,添加新的 author 列并为其创建外键:

+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int | 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联结表(junction table)

+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int | PRIMARY KEY FOREIGN KEY |
| photo_id | int | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+

请记得在 ORM 中将 Album 类注册到你的连接中:

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

你可以使用 QueryBuilder 构建几乎任意复杂度的 SQL 查询。例如:

您可以使用 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()

该查询会选取所有已发布(published)且名称为 "My" 或 "Mishka" 的照片。 从第 5 条记录开始(分页偏移), 仅选取 10 条结果(分页限制), 查询结果按 id 降序排列。 相册(albums)使用左连接(left join), 元数据使用内连接(inner join)。

你将在应用中大量使用查询构建器。 更多 QueryBuilder 用法请参考此文档

示例

查看 sample 目录中的示例了解具体用法。

以下可直接克隆使用的代码库:

扩展

以下扩展可以简化 TypeORM 的使用并增强与其他模块的集成:

参与贡献

了解如何贡献请参阅此处,开发环境设置指南请查阅此处

本项目的发展离不开所有贡献者的支持:

赞助支持

开源项目维护艰难且耗时。若您希望投资 TypeORM 的未来,欢迎成为赞助商,这将让核心团队能投入更多时间改进框架并开发新功能。成为赞助商

金牌赞助商

成为金牌赞助商可享受核心贡献者提供的专属技术支持。成为金牌赞助商