@nativescript-community/typeorm
适用于 TypeScript、ES7、ES6、ES5 的数据映射 ORM。支持 MySQL、PostgreSQL、MariaDB、SQLite、MS SQL Server、Oracle、MongoDB 数据库。
npm i --save @nativescript-community/typeorm


Codecov

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

TypeORM 支持两种模式:活动记录数据映射,与目前所有其他 JavaScript ORM 不同,这意味着您可以以最有效的方式编写高质量、松耦合、可扩展、可维护的应用程序。

TypeORM 受其他 ORM(如 HibernateDoctrineEntity Framework)的影响很大。

功能

  • 支持 数据映射活动记录(任选其一)
  • 实体和列
  • 特定于数据库的列类型
  • 实体管理器
  • 仓库和自定义仓库
  • 清晰的面向对象关系模型
  • 关联(关系)
  • eager 和 lazy 关联
  • 单向、双向和自引用关联
  • 支持多种继承模式
  • 级联
  • 索引
  • 事务
  • 迁移和自动迁移生成
  • 连接池
  • 复制
  • 使用多个数据库连接
  • 处理多种数据库类型
  • 跨数据库和跨模式查询
  • 优雅的语法、灵活且强大的 QueryBuilder
  • 左连接和内连接
  • 使用连接的查询的分页
  • 查询缓存
  • 流式处理原始结果
  • 日志记录
  • 监听器和订阅者(钩子)
  • 支持闭包表模式
  • 在模型或单独的配置文件中声明模式
  • 以 json / xml / yml / env 格式配置连接
  • 支持 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
  • 生成的代码性能高、灵活、整洁且易于维护
  • 遵循所有可能的最佳实践
  • 命令行界面

更多...

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

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

@Entity()
export class User {

@PrimaryGeneratedColumn()
id: number;

@Column()
firstName: string;

@Column()
lastName: string;

@Column()
age: number;

}

您的领域逻辑如下所示

const repository = connection.getRepository(User);

const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await repository.save(user);

const allUsers = await repository.find();
const firstUser = await repository.findOne(1); // find by id
const timber = await repository.findOne({ firstName: "Timber", lastName: "Saw" });

await repository.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.findOne(1);
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });

await timber.remove();

安装

  1. 安装 npm 包

    npm install typeorm --save

  2. 您需要安装 reflect-metadata 模块

    npm install reflect-metadata --save

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

    import "reflect-metadata";

  3. 你可能需要安装 node typings

    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 config set @sap:registry https://npm.sap.com
      npm i @sap/hana-client
      npm i hdb-pool

      SAP Hana 支持由 Neptune Software 赞助。

    • 用于 MongoDB(实验性)

      npm install mongodb --save

    • 用于 NativeScriptreact-nativeCordova

      查看 支持的平台文档

    根据你使用的数据库,只安装其中之一。

TypeScript 配置

此外,请确保你使用的是 TypeScript 版本 3.3 或更高版本,并在 tsconfig.json 中启用了以下设置

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

你还可以在编译器选项的 lib 部分中启用 es6,或从 @types 安装 es6-shim

快速入门

开始使用 TypeORM 的最快方式是使用其 CLI 命令来生成一个入门项目。快速入门只能在 NodeJS 应用程序中使用 TypeORM 的情况下工作。如果你使用的是其他平台,请转到 分步指南

首先,全局安装 TypeORM

npm install typeorm -g

然后转到你想要创建新项目的目录,并运行以下命令

typeorm init --name MyProject --database mysql

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

此命令将在 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
│ └── index.ts // start point of your application
├── .gitignore // standard gitignore file
├── ormconfig.json // ORM and database connection configuration
├── package.json // node module dependencies
├── README.md // simple readme file
└── tsconfig.json // TypeScript compiler options

你还可以在现有的 node 项目上运行 typeorm init,但要小心 - 它可能会覆盖你已有的某些文件。

下一步是安装新项目的依赖项

cd MyProject
npm install

在安装过程中,编辑 ormconfig.json 文件,并将你自己的数据库连接配置选项放在那里

{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "test",
"password": "test",
"database": "test",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
]
}

特别是,你通常只需要配置 hostusernamepassworddatabase 以及可能 port 选项。

完成配置并安装所有节点模块后,你可以运行你的应用程序

npm start

就这样,你的应用程序应该成功运行并将在数据库中插入一个新的用户。你可以继续使用此项目并集成其他所需的模块,开始创建更多实体。

你可以通过运行 typeorm init --name MyProject --database mysql --express 命令生成一个安装了 express 的更高级的项目。

你可以通过运行 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;
}

现在,将添加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列是自动生成的(这被称为自动递增/序列/序列号/生成标识列)。为了做到这一点,你需要将@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;
}

列类型是数据库特定的。你可以设置数据库支持的任何列类型。有关支持的列类型的更多信息,请参阅这里

创建数据库连接

现在,当我们的实体创建完成后,让我们创建一个index.ts(或app.ts,无论你叫什么)文件,并在其中设置我们的连接

import "reflect-metadata";
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
Photo
],
synchronize: true,
logging: false
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));

在这个例子中,我们使用MySQL,但你可以使用任何其他支持的数据库。要使用其他数据库,只需将选项中的type更改为你要使用的数据库类型:mysqlmariadbpostgrescockroachdbsqlitemssqloraclecordovanativescriptreact-nativeexpomongodb。同时,请确保使用你自己的主机、端口、用户名、密码和数据库名设置。

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

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

从目录中加载所有实体

稍后,当我们创建更多实体时,我们需要将它们添加到我们的配置中的实体中。这很不方便,因此我们可以设置整个目录,从这个目录中所有实体都将连接并在我们的连接中使用。

import {createConnection} from "typeorm";

createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
__dirname + "/entity/*.js"
],
synchronize: true,
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));

但请注意这种方法。如果你使用ts-node,则需要指定.ts文件的路径。如果你使用outDir,则需要指定outDir目录中的.js文件路径。如果你使用outDir并且删除或重命名你的实体,请确保清除outDir目录并重新编译你的项目,因为当你删除源.ts文件时,它们的编译后的.js版本不会从输出目录中删除,并且仍然会被TypeORM加载,因为它们存在于outDir目录中。

运行应用程序

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

+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(100) | |
| description | text | |
| filename | varchar(255) | |
| views | int(11) | |
| isPublished | boolean | |
+-------------+--------------+----------------------------+

创建并插入照片到数据库中

现在让我们创建一个新的照片以将其保存到数据库中

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection(/*...*/).then(connection => {

let 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;

return connection.manager
.save(photo)
.then(photo => {
console.log("Photo has been saved. Photo id is", photo.id);
});

}).catch(error => console.log(error));

一旦你的实体被保存,它将获得一个新的生成id。《save》方法返回一个与传递给它的相同对象的实例。它不是对象的新副本,它修改其“id”并返回它。

使用async/await语法

让我们利用最新的ES8(ES2017)功能,并使用async/await语法

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

let 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 connection.manager.save(photo);
console.log("Photo has been saved");

}).catch(error => console.log(error));

使用实体管理器

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

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

/*...*/
let savedPhotos = await connection.manager.find(Photo);
console.log("All photos from the db: ", savedPhotos);

}).catch(error => console.log(error));

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

了解更多关于EntityManager的信息这里

使用仓库

现在让我们重构我们的代码,并使用Repository而不是EntityManager。每个实体都有自己的仓库,负责处理与其实体的所有操作。当你大量处理实体时,仓库比实体管理器更方便使用。

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

let 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;

let photoRepository = connection.getRepository(Photo);

await photoRepository.save(photo);
console.log("Photo has been saved");

let savedPhotos = await photoRepository.find();
console.log("All photos from the db: ", savedPhotos);

}).catch(error => console.log(error));

了解更多关于仓库的信息这里

从数据库加载数据

让我们尝试使用仓库进行更多加载操作

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

/*...*/
let allPhotos = await photoRepository.find();
console.log("All photos from the db: ", allPhotos);

let firstPhoto = await photoRepository.findOne(1);
console.log("First photo from the db: ", firstPhoto);

let meAndBearsPhoto = await photoRepository.findOne({ name: "Me and Bears" });
console.log("Me and Bears photo from the db: ", meAndBearsPhoto);

let allViewedPhotos = await photoRepository.find({ views: 1 });
console.log("All viewed photos: ", allViewedPhotos);

let allPublishedPhotos = await photoRepository.find({ isPublished: true });
console.log("All published photos: ", allPublishedPhotos);

let [allPhotos, photosCount] = await photoRepository.findAndCount();
console.log("All photos: ", allPhotos);
console.log("Photos count: ", photosCount);

}).catch(error => console.log(error));

在数据库中更新

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

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

/*...*/
let photoToUpdate = await photoRepository.findOne(1);
photoToUpdate.name = "Me, my friends and polar bears";
await photoRepository.save(photoToUpdate);

}).catch(error => console.log(error));

现在数据库中的照片id = 1将被更新。

从数据库中删除

现在让我们从数据库中删除我们的照片

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

/*...*/
let photoToRemove = await photoRepository.findOne(1);
await photoRepository.remove(photoToRemove);

}).catch(error => console.log(error));

现在数据库中的照片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(type => 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 {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";

createConnection(/*...*/).then(async connection => {

// create a photo
let 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 a photo metadata
let 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
let photoRepository = connection.getRepository(Photo);
let metadataRepository = connection.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 relation between metadata and photo is created in the database too");

}).catch(error => console.log(error));

关系的一方

关系可以是单向的或双向的。目前,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(type => 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(type => PhotoMetadata, photoMetadata => photoMetadata.photo)
metadata: PhotoMetadata;
}

photo => photo.metadata 是一个函数,它返回关系的反向一侧的名称。在这里,我们显示 Photo 类的 metadata 属性是我们在 Photo 类中存储 PhotoMetadata 的地方。您可以选择不传递返回照片属性的函数,而是简单地传递一个字符串到 @OneToOne 装饰器,如 "metadata"。但为了使重构更容易,我们使用了这种函数类型的方法。

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

加载具有其关系的对象

现在让我们在单个查询中加载我们的照片及其照片元数据。有两种方法可以做到这一点 - 使用 find* 方法或使用 QueryBuilder 功能。让我们首先使用 find* 方法。 find* 方法允许您指定具有 FindOneOptions / FindManyOptions 接口的对象。

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";

createConnection(/*...*/).then(async connection => {

/*...*/
let photoRepository = connection.getRepository(Photo);
let photos = await photoRepository.find({ relations: ["metadata"] });

}).catch(error => console.log(error));

在这里,photos 将包含来自数据库的照片数组,并且每张照片都将包含其照片元数据。有关 Find 选项的更多信息,请参阅 此文档

使用 find 选项很好,而且非常简单,但如果您需要更复杂的查询,您应该使用 QueryBuilderQueryBuilder 允许以优雅的方式使用更复杂的查询

import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";

createConnection(/*...*/).then(async connection => {

/*...*/
let photos = await connection
.getRepository(Photo)
.createQueryBuilder("photo")
.innerJoinAndSelect("photo.metadata", "metadata")
.getMany();


}).catch(error => console.log(error));

QueryBuilder 允许创建和执行几乎任何复杂性的 SQL 查询。当您使用 QueryBuilder 时,请想象您正在创建一个 SQL 查询。在这个例子中,“photo”和“metadata”是应用于所选照片的别名。您使用别名来访问所选数据的列和属性。

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

我们可以设置关系中的级联选项,在需要我们的相关对象在另一个对象保存时保存的情况下。让我们稍微改变一下我们照片的 @OneToOne 装饰器

export class Photo {
/// ... other columns

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

使用 cascade 允许我们不再分别保存照片和元数据对象。现在我们可以简单地保存照片对象,由于级联选项,元数据对象将被自动保存。

createConnection(options).then(async connection => {

// create photo object
let 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
let 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
let photoRepository = connection.getRepository(Photo);

// saving a photo also save the metadata
await photoRepository.save(photo);

console.log("Photo is saved, photo metadata is saved too.")

}).catch(error => console.log(error));

请注意,我们现在设置照片的 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(type => 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(type => 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(type => Photo, photo => photo.albums)
@JoinTable()
photos: Photo[];
}

@JoinTable 是必需的,用于指定这是关系的所有者一侧。

现在让我们将关系的反向一侧添加到 Photo 类中

export class Photo {
/// ... other columns

@ManyToMany(type => 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 |
+-------------+--------------+----------------------------+

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

const options: ConnectionOptions = {
// ... other options
entities: [Photo, PhotoMetadata, Author, Album]
};

现在让我们将相册和照片插入到我们的数据库中

let connection = await createConnection(options);

// create a few albums
let album1 = new Album();
album1.name = "Bears";
await connection.manager.save(album1);

let album2 = new Album();
album2.name = "Me";
await connection.manager.save(album2);

// create a few photos
let 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 connection.manager.save(photo);

// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await connection
.getRepository(Photo)
.findOne(1, { relations: ["albums"] });

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 查询。例如,您可以这样做

let photos = await connection
.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 的更多信息,请参阅 此处

示例

请参阅 sample 中的示例,以了解使用示例。

有一些仓库您可以克隆并开始使用

扩展

有几个扩展简化了使用 TypeORM 和将其与其他模块集成的工作

贡献

了解贡献信息,请参阅这里,以及如何设置您的开发环境这里

本项目的存在离不开所有贡献者

赞助商

开源项目既困难又耗时。如果您想投资TypeORM的未来,您可以成为赞助商,让我们的核心团队有更多时间专注于TypeORM的改进和新功能。成为赞助商

金牌赞助商

成为金牌赞助商,并获得我们核心贡献者的高级技术支持。成为金牌赞助商