README-zh_CN
TypeORM 是一个 ORM 框架,它可以运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES5,ES6,ES7,ES8)一起使用。 它的目标是始终支持最新的 JavaScript 特性并提供额外的特性以帮助你开发任何使用数据库的(不管是只有几张表的小型应用还是拥有多数据库的大型企业应用)应用程序。
不同于现有的所有其他 JavaScript ORM 框架,TypeORM 支持 Data MapperActive Record 模式,这意味着你可以以最高效的方式编写高质量的、松耦合的、可扩展的、可维护的应用程序。
TypeORM 参考了很多其他优秀 ORM 的实现, 比如 Hibernate, DoctrineEntity Framework
TypeORM 的一些特性:
    同时支持 DataMapperActiveRecord (随你选择)
    实体和列
    数据库特性列类型
    实体管理
    存储库和自定义存储库
    清晰的对象关系模型
    关联(关系)
    贪婪和延迟关系
    单向的,双向的和自引用的关系
    支持多重继承模式
    级联
    索引
    事务
    迁移和自动迁移
    连接池
    主从复制
    使用多个数据库连接
    使用多个数据库类型
    跨数据库和跨模式查询
    优雅的语法,灵活而强大的 QueryBuilder
    左联接和内联接
    使用联查查询的适当分页
    查询缓存
    原始结果流
    日志
    监听者和订阅者(钩子)
    支持闭包表模式
    在模型或者分离的配置文件中声明模式
    json / xml / yml / env 格式的连接配置
    支持 MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / SAP Hana / sql.js
    支持 MongoDB NoSQL 数据库
    可在 NodeJS / 浏览器 / Ionic / Cordova / React Native / Expo / Electron 平台上使用
    支持 TypeScript 和 JavaScript
    生成高性能、灵活、清晰和可维护的代码
    遵循所有可能的最佳实践
    命令行工具
还有更多...
通过使用 TypeORM 你的 models 看起来如下:
1
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
2
3
@Entity()
4
export class User {
5
@PrimaryGeneratedColumn()
6
id: number;
7
8
@Column()
9
firstName: string;
10
11
@Column()
12
lastName: string;
13
14
@Column()
15
age: number;
16
}
Copied!
逻辑操作如下:
1
const user = new User();
2
user.firstName = "Timber";
3
user.lastName = "Saw";
4
user.age = 25;
5
await repository.save(user);
6
7
const allUsers = await repository.find();
8
const firstUser = await repository.findOne(1); // 根据id查找
9
const timber = await repository.findOne({ firstName: "Timber", lastName: "Saw" });
10
11
await repository.remove(timber);
Copied!
或者,如果你更喜欢使用 ActiveRecord 模式,也可以这样用:
1
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
2
3
@Entity()
4
export class User extends BaseEntity {
5
@PrimaryGeneratedColumn()
6
id: number;
7
8
@Column()
9
firstName: string;
10
11
@Column()
12
lastName: string;
13
14
@Column()
15
age: number;
16
}
Copied!
逻辑操作如下所示:
1
const user = new User();
2
user.firstName = "Timber";
3
user.lastName = "Saw";
4
user.age = 25;
5
await user.save();
6
7
const allUsers = await User.find();
8
const firstUser = await User.findOne(1);
9
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });
10
11
await timber.remove();
Copied!

入门

安装

    1.
    通过 npm 安装:
    npm install typeorm --save
    2.
    你还需要安装 reflect-metadata:
    npm install reflect-metadata --save
    并且需要在应用程序的全局位置导入(例如在app.ts中)
    import "reflect-metadata";
    3.
    你可能还需要安装 node typings(以此来使用 Node 的智能提示):
    npm install @types/node --save
    4.
    安装数据库驱动:
      MySQL 或者 MariaDB
      npm install mysql --save (也可以安装 mysql2)
      PostgreSQL
      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 驱动程序正常工作,需要按照其站点中的安装说明进行操作。
      MongoDB (试验性)
      npm install mongodb --save
      NativeScript, react-nativeCordova
      查看 支持的平台
      SAP Hana
      1
      npm config set @sap:registry https://npm.sap.com
      2
      npm i @sap/hana-client
      3
      npm i hdb-pool
      Copied!
TypeScript 配置
此外,请确保你使用的 TypeScript 编译器版本是3.3或更高版本,并且已经在 tsconfig.json 中启用了以下设置:
1
"emitDecoratorMetadata": true,
2
"experimentalDecorators": true,
Copied!
除此之外,你可能还需要在编译器选项的 lib 中启用 es6,或者安装 es6-shim@types

快速开始

快速上手 TypeORM 的方法是使用其 CLI 命令生成启动项目。 但是只有在 NodeJS 应用程序中使用 TypeORM 时,此操作才有效。如果你使用的是其他平台,请查看分步指南
首先全局安装 TypeORM:
1
npm install typeorm -g
Copied!
然后转到要创建新项目的目录并运行命令:
1
typeorm init --name MyProject --database mysql
Copied!
其中 name 是项目的名称,database 是将使用的数据库。
数据库可以是以下值之一: mysqlmariadbpostgressqlitemssqloraclemongodbcordovareact-nativeexponativescript.
此命令将在 MyProject 目录中生成一个包含以下文件的新项目:
1
MyProject
2
├── src // TypeScript 代码
3
│ ├── entity // 存储实体(数据库模型)的位置
4
│ │ └── User.ts // 示例 entity
5
│ ├── migration // 存储迁移的目录
6
│ └── index.ts // 程序执行主文件
7
├── .gitignore // gitignore文件
8
├── ormconfig.json // ORM和数据库连接配置
9
├── package.json // node module 依赖
10
├── README.md // 简单的 readme 文件
11
└── tsconfig.json // TypeScript 编译选项
Copied!
你还可以在现有 node 项目上运行 typeorm init,但要注意,此操作可能会覆盖已有的某些文件。
接下来安装项目依赖项:
1
$ cd MyProject
2
$ npm install
Copied!
在安装过程中,编辑 ormconfig.json 文件并在其中编辑自己的数据库连接配置选项:
1
{
2
"type": "mysql",
3
"host": "localhost",
4
"port": 3306,
5
"username": "test",
6
"password": "test",
7
"database": "test",
8
"synchronize": true,
9
"logging": false,
10
"entities": ["src/entity/**/*.ts"],
11
"migrations": ["src/migration/**/*.ts"],
12
"subscribers": ["src/subscriber/**/*.ts"]
13
}
Copied!
绝大多数情况下,你只需要配置 host, username, password, database 或者 port 即可。
完成配置并安装所有 node modules 后,即可运行应用程序:
1
$ npm start
Copied!
至此你的应用程序应该成功运行并将新用户插入数据库。你可以继续使用此项目并集成所需的其他模块并创建更多实体。
你可以通过运行 typeorm init --name MyProject --database mysql --express 来生成一个更高级的 Express 项目

分步指南

你对 ORM 有何期待?期望它将为你创建数据库表,并且无需编写大量难以维护的 SQL 语句来查找/插入/更新/删除数据。本指南将向你展示如何从头开始设置 TypeORM 并实现这些操作。

创建一个模型

使用数据库从创建表开始。如何告诉 TypeORM 创建数据库表?答案是 - 通过模型。 应用程序中的模型即是数据库中的表。
举个例子, 你有一个 Photo 模型:
1
export class Photo {
2
id: number;
3
name: string;
4
description: string;
5
filename: string;
6
views: number;
7
}
Copied!
并且希望将 photos 存储在数据库中。要在数据库中存储内容,首先需要一个数据库表,并从模型中创建数据库表。但是并非所有模型,只有定义为entities的模型。

创建一个实体

实体是由 @Entity 装饰器装饰的模型。将为此类模型创建数据库表。你可以使用 TypeORM 处理各处的实体,可以使用它们 load/insert/update/remove 并执行其他操作。
让我们将 Photo 模型作为一个实体
1
import { Entity } from "typeorm";
2
3
@Entity()
4
export class Photo {
5
id: number;
6
name: string;
7
description: string;
8
filename: string;
9
views: number;
10
isPublished: boolean;
11
}
Copied!
现在,将为 Photo 实体创建一个数据库表,我们将能够在应用程序中的任何位置使用它。 我们已经创建了一个数据库表,但是没有指明哪个字段属于哪一列,下面让我们在数据库表中创建列。

添加表列

要添加数据库列,你只需要将要生成的实体属性加上 @Column 装饰器。
1
import { Entity, Column } from "typeorm";
2
3
@Entity()
4
export class Photo {
5
@Column()
6
id: number;
7
8
@Column()
9
name: string;
10
11
@Column()
12
description: string;
13
14
@Column()
15
filename: string;
16
17
@Column()
18
views: number;
19
20
@Column()
21
isPublished: boolean;
22
}
Copied!
现在 id, name, description, filename, viewsisPublished 列将会被添加到 photo 表中。 数据库中的列类型是根据你使用的属性类型推断的,例如: number 将被转换为 integerstring 将转换为 varcharboolean 转换为 bool 等。但你也可以通过 @Column 装饰器中隐式指定列类型来使用数据库支持的任何列类型。
我们已经生成了一个包含列的数据库表,但是别忘了,每个数据库表必须具有包含主键的列。

创建主列

每个必须至少有一个主键列。这是必须的,你无法避免。要使列成为主键,你需要使用 @PrimaryColumn 装饰器。
1
import { Entity, Column, PrimaryColumn } from "typeorm";
2
3
@Entity()
4
export class Photo {
5
@PrimaryColumn()
6
id: number;
7
8
@Column()
9
name: string;
10
11
@Column()
12
description: string;
13
14
@Column()
15
filename: string;
16
17
@Column()
18
views: number;
19
20
@Column()
21
isPublished: boolean;
22
}
Copied!

创建自动生成的列

假设你希望 id 列自动生成(这称为 auto-increment/sequence/serial/generated identity column)。为此你需要将@PrimaryColumn 装饰器更改为 @PrimaryGeneratedColumn 装饰器:
1
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
2
3
@Entity()
4
export class Photo {
5
@PrimaryGeneratedColumn()
6
id: number;
7
8
@Column()
9
name: string;
10
11
@Column()
12
description: string;
13
14
@Column()
15
filename: string;
16
17
@Column()
18
views: number;
19
20
@Column()
21
isPublished: boolean;
22
}
Copied!

列数据类型

接下来,让我们修改数据类型。默认情况下,字符串被映射到一个 varchar(255) 类型(取决于数据库类型)。 数字被映射到一个类似 integer 类型(取决于数据库类型)。但是我们不希望所有的列都是有限的 varchars 或 integer,让我们修改下代码以设置想要的数据类型:
1
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
2
3
@Entity()
4
export class Photo {
5
@PrimaryGeneratedColumn()
6
id: number;
7
8
@Column({
9
length: 100
10
})
11
name: string;
12
13
@Column("text")
14
description: string;
15
16
@Column()
17
filename: string;
18
19
@Column("double")
20
views: number;
21
22
@Column()
23
isPublished: boolean;
24
}
Copied!
列类型是特定于数据库的。你可以设置数据库支持的任何列类型。有关支持的列类型的更多信息,请参见此处

创建数据库的连接

当实体被创建后,让我们创建一个index.ts(或app.ts,无论你怎么命名)文件,并配置数据库连接::
1
import "reflect-metadata";
2
import { createConnection } from "typeorm";
3
import { Photo } from "./entity/Photo";
4
5
createConnection({
6
type: "mysql",
7
host: "localhost",
8
port: 3306,
9
username: "root",
10
password: "admin",
11
database: "test",
12
entities: [Photo],
13
synchronize: true,
14
logging: false
15
})
16
.then(connection => {
17
// 这里可以写实体操作相关的代码
18
})
19
.catch(error => console.log(error));
Copied!
我们在此示例中使用 MySQL,你可以使用任何其他受支持的数据库。要使用其他数据库,只需将选项中的 type 更改为希望使用的数据库类型:mysqlmariadbpostgressqlitemssqloraclecordovanativescriptreact-nativeexpomongodb。同时还要确保 host, port, username, passworddatabase 正确设置。
我们将 Photo 实体添加到此连接的实体列表中,并且所有需要使用的实体都必须加进来。
设置 synchronize 可确保每次运行应用程序时实体都将与数据库同步。

加载目录中所有实体

之后当我们创建更多实体时,都需要一一将它们添加到配置中的实体中,但是这不是很方便,所以我们可以设置加载整个目录,从中连接所有实体并使用:
1
import { createConnection } from "typeorm";
2
3
createConnection({
4
type: "mysql",
5
host: "localhost",
6
port: 3306,
7
username: "root",
8
password: "admin",
9
database: "test",
10
entities: [__dirname + "/entity/*.js"],
11
synchronize: true
12
})
13
.then(connection => {
14
// 这里可以写实体操作相关的代码
15
})
16
.catch(error => console.log(error));
Copied!
但要小心使用这种方法。 如果使用的是 ts-node,则需要指定 .ts 文件的路径。 如果使用的是 outDir,那么需要在 outDir 目录中指定 .js 文件的路径。 如果使用 outDir,当你删除或重命名实体时,请确保清除 outDir 目录并再次重新编译项目,因为当你删除 .ts 源文件时,其编译的 .js 文件不会从输出目录中删除,并且 TypeORM 依然会从 outDir 中加载这些文件,从而导致异常。

启动应用

现在可以启动 app.ts,启动后可以发现数据库自动被初始化,并且 Photo 这个表也会创建出来。
1
+-------------+--------------+----------------------------+
2
| photo |
3
+-------------+--------------+----------------------------+
4
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
5
| name | varchar(100) | |
6
| description | text | |
7
| filename | varchar(255) | |
8
| views | int(11) | |
9
| isPublished | boolean | |
10
+-------------+--------------+----------------------------+
Copied!

添加和插入 photo

现在创建一个新的 photo 存到数据库:
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
4
createConnection(/*...*/)
5
.then(connection => {
6
let photo = new Photo();
7
photo.name = "Me and Bears";
8
photo.description = "I am near polar bears";
9
photo.filename = "photo-with-bears.jpg";
10
photo.views = 1;
11
photo.isPublished = true;
12
13
return connection.manager.save(photo).then(photo => {
14
console.log("Photo has been saved. Photo id is", photo.id);
15
});
16
})
17
.catch(error => console.log(error));
Copied!
保存实体后,将获得新生成的 ID。 save 方法返回传递给它的同一对象的实例,但并不是对象的新副本,只是修改了它的"id"并返回。

使用 async/await 语法

我们可以使用ES8(ES2017)的新特性,并使用 async/await 语法代替:
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
4
createConnection(/*...*/)
5
.then(async connection => {
6
let photo = new Photo();
7
photo.name = "Me and Bears";
8
photo.description = "I am near polar bears";
9
photo.filename = "photo-with-bears.jpg";
10
photo.views = 1;
11
photo.isPublished = true;
12
13
await connection.manager.save(photo);
14
console.log("Photo has been saved");
15
})
16
.catch(error => console.log(error));
Copied!

使用 Entity Manager

我们刚创建了一张新 photo 表并将其保存在数据库中。通过使用 EntityManager 你可以操纵应用中的任何实体。
例如,加载已经保存的实体:
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
4
createConnection(/*...*/)
5
.then(async connection => {
6
/*...*/
7
let savedPhotos = await connection.manager.find(Photo);
8
console.log("All photos from the db: ", savedPhotos);
9
})
10
.catch(error => console.log(error));
Copied!
savedPhotos 是一个 Photo 对象数组,其中包含从数据库加载的数据。
了解更多有关 EntityManager 的信息。

使用 Repositories

现在让我们重构之前的代码,并使用 Repository 替代 EntityManager。每个实体都有自己的repository,可以处理其实体的所有操作。当你经常处理实体时,Repositories 比 EntityManagers 更方便使用:
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
4
createConnection(/*...*/)
5
.then(async connection => {
6
let photo = new Photo();
7
photo.name = "Me and Bears";
8
photo.description = "I am near polar bears";
9
photo.filename = "photo-with-bears.jpg";
10
photo.views = 1;
11
photo.isPublished = true;
12
13
let photoRepository = connection.getRepository(Photo);
14
15
await photoRepository.save(photo);
16
console.log("Photo has been saved");
17
18
let savedPhotos = await photoRepository.find();
19
console.log("All photos from the db: ", savedPhotos);
20
})
21
.catch(error => console.log(error));
Copied!
了解更多有关 Repository 的信息。

从数据库加载

让我们使用 Repository 尝试更多的加载操作:
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
4
createConnection(/*...*/)
5
.then(async connection => {
6
/*...*/
7
let allPhotos = await photoRepository.find();
8
console.log("All photos from the db: ", allPhotos);
9
10
let firstPhoto = await photoRepository.findOne(1);
11
console.log("First photo from the db: ", firstPhoto);
12
13
let meAndBearsPhoto = await photoRepository.findOne({ name: "Me and Bears" });
14
console.log("Me and Bears photo from the db: ", meAndBearsPhoto);
15
16
let allViewedPhotos = await photoRepository.find({ views: 1 });
17
console.log("All viewed photos: ", allViewedPhotos);
18
19
let allPublishedPhotos = await photoRepository.find({ isPublished: true });
20
console.log("All published photos: ", allPublishedPhotos);
21
22
let [allPhotos, photosCount] = await photoRepository.findAndCount();
23
console.log("All photos: ", allPhotos);
24
console.log("Photos count: ", photosCount);
25
})
26
.catch(error => console.log(error));
Copied!

从数据库中更新

让我们从数据库加载出 photo,更新并保存到数据库:
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
4
createConnection(/*...*/)
5
.then(async connection => {
6
/*...*/
7
let photoToUpdate = await photoRepository.findOne(1);
8
photoToUpdate.name = "Me, my friends and polar bears";
9
await photoRepository.save(photoToUpdate);
10
})
11
.catch(error => console.log(error));
Copied!
这个 id = 1 的 photo 在数据库中就成功更新了。

从数据库中删除

让我们从数据库中删除 Photo:
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
4
createConnection(/*...*/)
5
.then(async connection => {
6
/*...*/
7
let photoToRemove = await photoRepository.findOne(1);
8
await photoRepository.remove(photoToRemove);
9
})
10
.catch(error => console.log(error));
Copied!
这个 id = 1的 photo 在数据库中被移除了。

创建一对一的关系

要与另一个类创建一对一的关系。先在 PhotoMetadata.ts 中创建一个新类。此 PhotoMetadata 类应包含 photo 的其他元信息:
1
import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from "typeorm";
2
import { Photo } from "./Photo";
3
4
@Entity()
5
export class PhotoMetadata {
6
@PrimaryGeneratedColumn()
7
id: number;
8
9
@Column("int")
10
height: number;
11
12
@Column("int")
13
width: number;
14
15
@Column()
16
orientation: string;
17
18
@Column()
19
compressed: boolean;
20
21
@Column()
22
comment: string;
23
24
@OneToOne(type => Photo)
25
@JoinColumn()
26
photo: Photo;
27
}
Copied!
这里我们使用了一个名为 @OneToOne 的新装饰器,它允许我们在两个实体之间创建一对一的关系。 type => Photo 是一个函数,返回我们想要与之建立关系的实体的类。由于特定于语言的关系,我们只能使用一个返回类的函数,而不是直接使用该类。 同时也可以把它写成 ()=> Photo,但是 type => Photo 显得代码更有可读性。type 变量本身不包含任何内容。
我们还添加了一个 @JoinColumn 装饰器,表明实体键的对应关系。关系可以是单向的或双向的。但是只有一方可以拥有。在关系的所有者方需要使用 @JoinColumn 装饰器。
如果运行该应用程序,你将看到一个新生成的表,它将包含一个带有外键的列:
1
+-------------+--------------+----------------------------+
2
| photo_metadata |
3
+-------------+--------------+----------------------------+
4
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
5
| height | int(11) | |
6
| width | int(11) | |
7
| comment | varchar(255) | |
8
| compressed | boolean | |
9
| orientation | varchar(255) | |
10
| photoId | int(11) | FOREIGN KEY |
11
+-------------+--------------+----------------------------+
Copied!

保存一对一的关系

现在让我们来创建一个 photo,它的元信息将它们互相连接起来。
1
import { createConnection } from "typeorm";
2
import { Photo } from "./entity/Photo";
3
import { PhotoMetadata } from "./entity/PhotoMetadata";
4
5
createConnection(/*...*/)
6
.then(async connection => {
7
// 创建 photo
8
let photo = new Photo();
9
photo.name = "Me and Bears";
10
photo.description = "I am near polar bears";
11
photo.filename = "photo-with-bears.jpg";
12
photo.views = 1;
13
photo.isPublished =<