跳转到主要内容

开始入门



Codecov

TypeORM 是一个ORM,可以在 NodeJS、浏览器、Cordova、PhoneGap、Ionic、React Native、NativeScript、Expo 和 Electron 平台上运行,并且可以与 TypeScript 和 JavaScript(ES5、ES6、ES7、ES8)一起使用。其目标是始终支持最新的 JavaScript 功能并提供额外功能,帮助您开发使用数据库的任何类型的应用程序 - 从具有几个表的小型应用程序到具有多个数据库的大型企业级应用程序。

TypeORM 支持 Active RecordData Mapper 模式,与当前存在的所有其他 JavaScript ORM 不同,这意味着您可以用最富有成效的方式编写高质量、松散耦合、可扩展、可维护的应用程序。

TypeORM 受到其他 ORM 的很大影响,例如 HibernateDoctrineEntity Framework

特点

  • 支持 DataMapperActiveRecord(由您选择)。
  • 实体和列。
  • 数据库特定的列类型。
  • 实体管理器。
  • 存储库和自定义存储库。
  • 清晰的对象关系模型。
  • 关联(关系)。
  • 急切和懒惰的关系。
  • 单向、双向和自引用关系。
  • 支持多种继承模式。
  • 级联。
  • 索引。
  • 事务。
  • 迁移和自动生成迁移。
  • 连接池。
  • 复制。
  • 使用多个数据库实例。
  • 使用多种数据库类型。
  • 跨数据库和跨模式查询。
  • 优雅的语法、灵活且强大的 QueryBuilder。
  • 左连接和内连接。
  • 使用连接的查询的正确分页。
  • 查询缓存。
  • 流式原始结果。
  • 日志。
  • 监听器和订阅者(钩子)。
  • 支持闭包表模式。
  • 模型中的模式声明或单独的配置文件。
  • 支持 MySQL / MariaDB / Postgres / CockroachDB / SQLite / Microsoft SQL Server / Oracle / SAP Hana / sql.js。
  • 支持 MongoDB NoSQL 数据库。
  • 在 NodeJS / 浏览器 / 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 = 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,
}) // 通过 id 查找
const timber = await userRepository.findOneBy({
firstName: "Timber",
lastName: "Saw",
}) // 通过 firstName 和 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 --save

  2. 您需要安装 reflect-metadata shim:

    npm install reflect-metadata --save

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

    import "reflect-metadata"

  3. 您可能需要安装 node 类型:

    npm install @types/node --save-dev

  4. 安装数据库驱动程序:

    • 对于 MySQLMariaDB

      npm install mysql --save (您也可以安装 mysql2)

    • 对于 PostgreSQLCockroachDB

      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 Software 的赞助成为可能。

    • 对于 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

      # 用包含服务帐户密钥的 JSON 文件的路径替换 KEY_PATH。

      要在模拟器上使用 Spanner,您应该设置 SPANNER_EMULATOR_HOST 环境变量:

      # Linux/macOS
      export SPANNER_EMULATOR_HOST=localhost:9010

      # Windows
      set SPANNER_EMULATOR_HOST=localhost:9010
    • 对于 MongoDB (实验性)

      npm install mongodb@^5.2.0 --save

    • 对于 NativeScriptreact-nativeCordova

      查看 支持平台的文档

    根据您使用的数据库,仅安装其中一个。

    TypeScript 配置

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

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

您还可能需要在编译器选项的 lib 部分启用 es6,或者从 @types 安装 es6-shim

快速开始

使用 TypeORM 的最快捷方式是使用其 CLI 命令生成一个初始项目。只有在 NodeJS 应用程序中使用 TypeORM 时,快速启动才能工作。如果您使用的是其他平台,请参阅分步指南

要使用 CLI 创建一个新项目,请运行以下命令:

npx typeorm init --name MyProject --database postgres

其中 name 是您的项目名称,database 是您将使用的数据库。数据库可以是以下值之一:mysqlmariadbpostgrescockroachdbsqlitemssqlsapspanneroraclemongodbcordovareact-nativeexponativescript

此命令将在 MyProject 目录中生成一个新项目,包含以下文件:

MyProject
├── src // 放置您的 TypeScript 代码的地方
│ ├── entity // 存储您的实体(数据库模型)的地方
│ │ └── User.ts // 示例实体
│ ├── migration // 存储您的迁移的地方
│ ├── data-source.ts // 数据源及所有连接配置
│ └── index.ts // 您应用程序的起点
├── .gitignore // 标准的 gitignore 文件
├── package.json // node 模块依赖项
├── README.md // 简单的自述文件
└── tsconfig.json // TypeScript 编译器选项

您也可以在现有的 node 项目上运行 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 选项。

完成配置并安装所有 node 模块后,您可以运行您的应用程序:

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 创建一个数据库表? 答案是 - 通过模型。 您的应用程序中的模型就是您的数据库表。

例如,您有一个 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
}

现在 photo 表中将添加 idnamedescriptionfilenameviewsisPublished 列。数据库中的列类型是从您使用的属性类型推断出来的,例如 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 列自动生成(这被称为自动增量/序列/序列号/生成的标识列)。要做到这一点,您需要将 @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) 的类型(取决于数据库类型)。数字映射到类似整数的类型(取决于数据库类型)。我们不希望所有列都是有限的 varchar 或整数。让我们设置正确的数据类型:

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,
})

// 要与数据库建立初始连接,注册所有实体
// 并 "同步" 数据库模式,调用新创建的数据库的 "initialize()" 方法
// 在您的应用程序引导时只需调用一次
AppDataSource.initialize()
.then(() => {
// 在这里您可以开始使用数据库
})
.catch((error) => console.log(error))

在此示例中,我们使用的是 Postgres,但您可以使用任何其他受支持的数据库。要使用另一个数据库,只需将选项中的 type 更改为您正在使用的数据库类型:mysqlmariadbpostgrescockroachdbsqlitemssqloraclesapspannercordovanativescriptreact-nativeexpomongodb。同时确保使用自己的主机、端口、用户名、密码和数据库设置。

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

设置 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 = "我和熊"
photo.description = "我在极地熊旁边"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true

await AppDataSource.manager.save(photo)
console.log("照片已保存。照片ID为", photo.id)

一旦实体被保存,它将获得一个新生成的ID。 save 方法返回一个与传入的对象相同的实例。 它不是对象的新副本,而是修改了其 "id" 并将其返回。

使用实体管理器

我们刚刚创建了一张新的照片并将其保存到数据库中。 我们使用了 EntityManager 来进行保存操作。 使用实体管理器,您可以操作应用程序中的任何实体。 例如,让我们加载已保存的实体:

import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"

const savedPhotos = await AppDataSource.manager.find(Photo)
console.log("数据库中的所有照片:", savedPhotos)

savedPhotos 将是一个包含从数据库加载的照片数据的 Photo 对象数组。

在此处了解更多关于 EntityManager 的信息 here

使用存储库

现在让我们重构我们的代码,使用 Repository 代替 EntityManager。 每个实体都有自己的存储库,用于处理与该实体的所有操作。 当您需要大量处理实体时,存储库比实体管理器更方便使用:

import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"

const photo = new Photo()
photo.name = "我和熊"
photo.description = "我在极地熊旁边"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true

const photoRepository = AppDataSource.getRepository(Photo)

await photoRepository.save(photo)
console.log("照片已保存")

const savedPhotos = await photoRepository.find()
console.log("数据库中的所有照片:", savedPhotos)

在此处了解更多关于 Repository 的信息 here

从数据库加载

让我们使用存储库尝试更多的加载操作:

import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"

const photoRepository = AppDataSource.getRepository(Photo)
const allPhotos = await photoRepository.find()
console.log("数据库中的所有照片:", allPhotos)

const firstPhoto = await photoRepository.findOneBy({
id: 1,
})
console.log("数据库中的第一张照片:", firstPhoto)

const meAndBearsPhoto = await photoRepository.findOneBy({
name: "我和熊",
})
console.log("数据库中的我和熊照片:", meAndBearsPhoto)

const allViewedPhotos = await photoRepository.findBy({ views: 1 })
console.log("所有已查看的照片:", allViewedPhotos)

const allPublishedPhotos = await photoRepository.findBy({ isPublished: true })
console.log("所有已发布的照片:", allPublishedPhotos)

const [photos, photosCount] = await photoRepository.findAndCount()
console.log("所有照片:", photos)
console.log("照片数量:", photosCount)

更新数据库中的数据

现在让我们从数据库中加载一张照片,更新它并保存:

import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"

const photoRepository = AppDataSource.getRepository(Photo)
const photoToUpdate = await photoRepository.findOneBy({
id: 1,
})
photoToUpdate.name = "我、我的朋友和北极熊"
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 装饰器是必需的。

如果运行该应用程序,你将会看到一个新生成的表,并且它将包含一个用于照片关系的外键列:

+-------------+--------------+----------------------------+
| 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"

// 创建一张照片
const photo = new Photo()
photo.name = "我和熊"
photo.description = "我在极地熊旁边"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true

// 创建照片的元数据
const metadata = new PhotoMetadata()
metadata.height = 640
metadata.width = 480
metadata.compressed = true
metadata.comment = "cybershoot"
metadata.orientation = "portrait"
metadata.photo = photo // 这样我们将它们关联起来

// 获取实体的存储库
const photoRepository = AppDataSource.getRepository(Photo)
const metadataRepository = AppDataSource.getRepository(PhotoMetadata)

// 首先保存照片
await photoRepository.save(photo)

// 照片已保存。现在我们需要保存照片的元数据
await metadataRepository.save(metadata)

// 完成
console.log("元数据已保存,并且在数据库中创建了元数据与照片之间的关联关系")

关系的反向方

关系可以是单向的或双向的。 目前,我们在 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 {
/* ... 其他列 */

@OneToOne(() => Photo, (photo) => photo.metadata)
@JoinColumn()
photo: Photo
}
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"

@Entity()
export class Photo {
/* ... 其他列 */

@OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
metadata: PhotoMetadata
}

photo => photo.metadata 是一个返回关系的反向方名称的函数。 在这里,我们表明 Photo 类中的 metadata 属性是我们在 Photo 类中存储 PhotoMetadata 的位置。 在 @OneToOne 装饰器中,您可以传递一个返回属性的函数,而不是属性本身,可以使用字符串形式的属性名,如 "metadata"。 但我们使用函数类型的方法来使重构更加简单。

请注意,我们只应在关系的一方使用 @JoinColumn 装饰器。 无论你将这个装饰器放在哪一方,它都将成为关系的拥有方。 关系的拥有方在数据库中包含一个带有外键的列。

在 ESM 项目中的关系

如果您在 TypeScript 项目中使用 ESM,您应该在关系属性中使用 Relation 包装类型,以避免循环依赖问题。 让我们修改我们的实体:

import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
Relation,
} from "typeorm"
import { Photo } from "./Photo"

@Entity()
export class PhotoMetadata {
/* ... 其他列 */

@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 {
/* ... 其他列 */

@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 将包含从数据库中获取的照片数组,每张照片都包含其照片元数据。 在 这个文档 中了解更多关于 Find Options 的信息。

使用 find options 是非常简单和方便的,但是如果您需要更复杂的查询,应该使用 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" 是应用于选定的照片的别名。 您可以使用别名来访问选定数据的列和属性。

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

我们可以在关联中设置级联选项,以便在保存其他对象时自动保存关联的对象。 让我们稍微修改照片的 @OneToOne 装饰器:

export class Photo {
// ... 其他列

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

使用 cascade 允许我们在保存照片对象时自动保存元数据对象,因为有了级联选项。

import { AppDataSource } from "./index"

// 创建照片对象
const photo = new Photo()
photo.name = "我和熊"
photo.description = "我在极地熊旁边"
photo.filename = "photo-with-bears.jpg"
photo.isPublished = true

// 创建照片元数据对象
const metadata = new PhotoMetadata()
metadata.height = 640
metadata.width = 480
metadata.compressed = true
metadata.comment = "cybershoot"
metadata.orientation = "portrait"

photo.metadata = metadata // 这样我们将它们关联起来

// 获取存储库
const photoRepository = AppDataSource.getRepository(Photo)

// 保存照片也会自动保存元数据
await photoRepository.save(photo)

console.log("照片已保存,照片元数据也已保存。")

请注意,现在我们设置了照片的 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) // 注意:我们将在下面的 Photo 类中创建 author 属性
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 {
/* ... 其他列 */

@ManyToOne(() => Author, (author) => author.photos)
author: Author
}

在多对一 / 一对多关系中,拥有方始终是多对一。 这意味着使用 @ManyToOne 的类将存储关联对象的 id。

运行应用程序后,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 {
// ... 其他列

@ManyToMany(() => Album, (album) => album.photos)
albums: Album[]
}

在运行应用程序后,ORM将创建一个名为album_photos_photo_albums连接表(junction table):

+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int(11) | PRIMARY KEY FOREIGN KEY |
| photo_id | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+

不要忘记在ORM中将Album类注册到您的连接中:

const options: DataSourceOptions = {
// ... 其他选项
entities: [Photo, PhotoMetadata, Author, Album],
}

现在让我们向数据库插入相册和照片:

import { AppDataSource } from "./index"

// 创建几个相册
const album1 = new Album()
album1.name = "Bears"
await AppDataSource.manager.save(album1)

const album2 = new Album()
album2.name = "Me"
await AppDataSource.manager.save(album2)

// 创建几张照片
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)

// 现在我们的照片已保存,并且相册已与其关联
// 现在让我们加载它们:
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 查询。例如,您可以这样做:

const photos = await AppDataSource.getRepository(Photo)
.createQueryBuilder("photo") // 第一个参数是别名。别名是您正在选择的内容 - photos。必须指定它。
.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 的信息。

示例

查看 sample 中的示例,了解使用方法。

有几个存储库可以克隆并开始使用:

扩展

有几个扩展可以简化使用 TypeORM 并将其与其他模块集成: