TypeORM 快速指南



TypeORM - 简介

TypeORM 框架是一个对象关系映射 (ORM) 框架。一般来说,对象部分指的是应用程序中的域/模型,关系部分指的是关系数据库管理系统 (例如 Oracle、MySQL、MS-SQL、PostgreSQL 等) 中表之间的关系,最后映射部分指的是连接模型和表的动作。

ORM 是一种将实体与数据库表映射的工具。ORM 通过自动执行对象到表和表到对象的转换,简化了开发过程。一旦你可以在一个地方编写你的数据模型,更新、维护和重用代码就变得更容易了。

由于模型与应用程序的其余部分松散绑定,因此您可以更改它而不会对应用程序的其他部分产生任何硬依赖关系,并且可以在应用程序的任何地方轻松使用它。TypeORM 非常灵活,它将数据库系统从应用程序中抽象出来,并允许我们从使用面向对象编程 (OOPS) 的概念中获益。

概述

TypeORM 是一个在 node.js 上运行并在 TypeScript 中编写的对象关系映射库。TypeScript 是对 JavaScript 的改进,具有可选类型。TypeScript 是一种编译型语言。它在运行时不会被解释。TypeScript 编译器获取 TypeScript 文件 (.ts) 并将它们编译成 JavaScript 文件 (.js)。

TypeORM 支持多种数据库,如 MySQL、PostgreSQL、MariaDB、SQLite、MS SQL Server、Oracle、SAP Hana 和 WebSQL。TypeORM 是一款易于使用的 ORM,用于构建连接到数据库的新应用程序。TypeORM 的功能是特定于 RDBMS 的概念。

我们可以快速创建新的项目和微服务。它受到其他编程语言中类似工具(如 Hibernate、Doctrine、Entity Framework 等)的启发。

TypeORM 的特性

TypeORM 具有以下特性:

  • 根据您的模型自动创建数据库表架构。
  • 轻松插入、更新和删除数据库中的对象。
  • 创建表之间的映射(一对一、一对多和多对多)。
  • 提供简单的 CLI 命令。

Explore our latest online courses and learn new skills at your own pace. Enroll and become a certified expert to boost your career.

TypeORM 的优势

TypeORM 是一款易于使用的 ORM 框架,代码简单。它具有以下优势:

  • 高质量且松耦合的应用程序。
  • 可扩展的应用程序。
  • 易于与其他模块集成。
  • 完美适合从小型到企业级应用程序的任何架构。

TypeORM - 安装

本章介绍如何在您的机器上安装 TypeORM。在开始安装之前,请确保已安装 npm。要确认您是否已安装 npm,可以在终端中运行以下命令。

npm -v

它显示版本。如果未安装,请下载最新版本并在您的机器上安装。

安装 TypeORM

让我们使用 npm 模块在本地安装 TypeORM:

npm install typeorm --save

执行上述命令后,您将收到如下所示的响应:

+ typeorm@0.2.24 
+ 
added 1 package and audited 1236 packages in 4.729s

或者,要全局安装 TypeORM,请使用以下命令:

npm install typeorm -g

之后,使用 npm 安装可选包 reflect-metadata:

npm install reflect-metadata --save

您可以看到以下响应:

+ reflect-metadata@0.1.13 

added 1 package and audited 1236 packages in 2.426s

现在,安装另一个可选包 node typings,使用以下命令:

npm install @types/node --save

您可以看到以下响应:

+ @types/node@12.12.31 

added 1 package and audited 1236 packages in 2.167s

安装数据库驱动程序

在本节中,让我们为我们的数据库安装必要的节点包。

要安装MySQLMariaDB 包,请使用以下命令:

npm install mysql --save

您可以看到以下响应:

+ mysql@2.18.1 

added 1 package and audited 1236 packages in 4.36s

要安装PostgreSQL 包,请使用以下命令:

npm install pg --save

您可以看到以下响应:

+ pg@7.18.2 

added 1 package and audited 1236 packages in 2.863s

要安装SQLite 包,请使用以下命令:

npm install sqlite3 --save

您可以看到以下响应:

+ sqlite3@4.1.1 
added 48 packages from 45 contributors and audited 1396 packages in 7.31s

要安装Microsoft SQL Server 包,请使用以下命令:

npm install mssql --save

您的屏幕看起来与此类似,

+ mssql@6.2.0 

added 1 package and audited 1655 packages in 2.378s

要安装sql.js 包,请使用以下命令:

npm install sql.js --save

您可以看到以下响应:

+ sql.js@1.2.1 

added 1 package and audited 1655 packages in 6.462s

要安装Oracle 服务器包,请使用以下命令:

npm install oracledb --save

您可以看到以下响应:

+ oracledb@4.2.0 

added 1 package and audited 1655 packages in 2.265s

要安装mongodb 包,请使用以下命令:

npm install mongodb --save

您可以看到以下响应:

+ mongodb@3.5.5 

added 1 package and audited 1655 packages in 3.716s

TypeORM - 创建一个简单的项目

本章介绍如何创建简单的 TypeORM 应用程序。让我们创建一个名为“TypeORM”的新目录并移动到该目录。

cd /path/to/TypeORM/

语法

使用以下命令创建新项目:

typeorm init --name <project-name> --database <database-name>

示例

typeorm init --name FirstProject --database mysql

这里,

FirstProject 是您的项目名称,sqlite3 是数据库名称。执行上述命令后,您可以看到以下响应,

Project created inside /path/to/TypeORM/FirstProject directory

现在,进入我们的项目目录并使用npm 模块安装项目依赖项,

$ cd FirstProject 

$ npm install

项目结构

让我们了解一下新创建的项目FirstProject 的项目结构。

FirstProject 
├──> src 
 ├──> entity 
  └──> User.ts 
 ├──> migration 
 └──> index.ts 
├──> node_modules 
├──> ormconfig.json 
├──> package.json 
├──> package-lock.json 
└──> tsconfig.json

这里,

  • src - 包含应用程序的源代码,使用TypeScript 语言。它有一个文件index.ts 和两个子目录entitymigration
  • index.ts - 应用程序的入口点。
  • entity - 包含数据库模型。
  • migration - 包含数据库迁移代码。
  • node_modules - 本地保存的 npm 模块。
  • ormconfig.json - 应用程序的主要配置文件。它包含数据库配置详细信息和实体配置。
  • package.json - 包含节点模块依赖项。
  • package-lock.json - 自动生成的文件,与package.json 相关。
  • tsconfig.json - 包含 TypeScript 特定的编译器选项。

ormconfig.json 文件

让我们检查一下应用程序可用的配置选项。打开 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" 
   ], 
   "cli": { 
      "entitiesDir":"src/entity", "migrationsDir":"src/migration", "subscribersDir":"src/subscriber
   }    
 }

这里,

typehostusernamepassworddatabase 和 port 选项与数据库设置相关。mysql 可以使用以下配置进行配置:

{ 
   "type": "mysql", 
   "host": "localhost", 
   "port": 3306, 
   "username": "db_username", "password": "db_password", "database": "db_name" 
}
  • entities - 指向实体类的存放位置。
  • migrations - 指向迁移类的存放位置。
  • subscribers - 指向订阅者类的存放位置。
  • cli - 指向TypeORM CLI 自动生成代码时使用的选项

启动 MySql 服务器

在启动应用程序之前,请启动您的MySQL 服务器或您使用的任何数据库服务器,并确保它正在正常运行。

运行应用程序

一旦所有内容都配置好,我们可以使用以下命令执行应用程序:

npm start

您可以看到以下响应:

> FirstProject@0.0.1 start /Users/../../TypeORM/FirstProject 

> ts-node src/index.ts 

Inserting a new user into the database... Saved a new user with id: 1 Loading users from the database... Loaded users: [ User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 }] 

Here you can setup and run express/koa/any other framework.

应用程序将新用户插入数据库,然后从数据库中反向加载它,最后在控制台中显示加载的用户。我们已经成功创建了一个新的TypeORM 应用程序,对其进行了配置并运行了应用程序。

我们将在后续章节中详细讨论数据是如何执行的。

TypeORM - 连接 API

要与数据库交互,我们需要一个到数据库的连接对象。我们需要在执行数据库操作之前创建一个连接对象,并在数据库操作完成后必须终止它。让我们在本节中了解 TypeORM 提供的 Connection API。

创建新连接

在创建新连接之前,我们需要在ormconfig.json 配置文件中配置数据库连接详细信息。下面显示了一个示例连接详细信息:

ormconfig.json

{ 
   name: "firstconnection", 
   type: "mysql", 
   host: "localhost", 
   port: 3306, 
   username: "root", 
   password: "root", 
   database: "firstDB" 
}

这里,

  • name - 数据库连接的名称。
  • type - 数据库类型。
  • host - 数据库服务器的主机名。
  • port - 数据库服务器端口。
  • username - 拥有数据库访问权限的帐户名。
  • password - 上述帐户的密码。
  • database - 要连接的数据库的名称。

createConnection

CreateConnection 方法由 TypeORM 提供,用于创建新连接。其定义如下:

import { createConnection, Connection } from "typeorm"; 

const connection = await createConnection({ 

});

这里,createConnection 将使用 ormconfig.json 文件中指定的配置详细信息。

或者,您可以将连接 URL 定义为 createConnection 方法的参数,如下所示:

const connection = createConnection({ type: 'mysql', 
     url: 'localhost:8888/firstDB' 
})

这里,

createConnection 返回一个对象,该对象可用于打开/关闭到数据库的连接。

多个连接

TypeORM 还提供了一个创建多个数据库连接的选项。首先,配置文件 ormconfig.json 可用于指定多个数据库连接的详细信息。让我们在 ormconfig.json 中配置多个数据库,如下所示:

ormconfig.json

{  name: "firstconnection", 
   type: "mysql", 
   host: "localhost", 
   port: 3306, 
   username: "root", 
   password: "root", 
   database: "firstDB" 
}, 
{  name: "secondconnection", 
   type: "mysql", 
   host: "localhost", 
   port: 3306, 
   username: "root", 
   password: "root", 
   database: "secondDB" 
}, 
{  name: "thirdconnection", 
   type: "mysql", 
   host: "localhost", 
   port: 3306, 
   username: "root", 
   password: "root", 
   database: "thirdDB" 
}

现在,我们可以使用 createConnection 方法提供的参数来指定要创建连接对象的连接的名称,如下所示:

const firstconnection: Connection = await createConnection("firstconnection");

这里,

createConnection 将使用ormconfig.json 文件中指定的firstconnection 的配置详细信息来创建连接对象。

TypeORM 还提供了另一个 API createConnections 来一次创建多个连接,然后在需要时使用它,如下所示:

import { createConnections, Connection } from "typeorm"; 

const connections: Connection[] = await createConnections([ 

]);

这里,

connections 将所有连接对象作为数组保存。

ConnectionManager

TypeORM 还提供了一个名为 connectionManager 的 API 来创建连接。其定义如下:

import {getConnectionManager, ConnectionManager, Connection} from "typeorm"; 

const connectionManager = getConnectionManager(); 

const connection = connectionManager.create({ 

}); 
await connection.connect();

TypeORM 优先使用createConnection 而不是ConnectionManager 来创建连接对象。

TypeORM - 实体

实体是字段和相关数据库操作的集合。它用于将数据库表及其字段与实体及其属性映射。本章详细介绍了 TypeORM 实体。

介绍

让我们在代码中创建一个简单的 Entity 类。移动到您的项目根目录,进入 src 文件夹并移动到 entity 文件夹。现在,创建一个 TypeScript 文件 Student.ts 并输入以下代码:

Student.ts

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

@Entity() 
export class Student {   

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   Name: string; 
   
   @Column() 
   age: number; 
}

这里,

  • Entity() 装饰器类用于表示Student 类是一个实体。
  • PrimaryGeneratedColumn() 装饰器类用于表示 id 列是Student 实体的主键列。
  • Column() 装饰器类用于表示Student 实体的其他列,例如NameAge

现在,已创建 Entity 类Student。TypeORM 将自动生成一个与Student 实体对应的表到我们的数据库中,并且它将被命名为student。现在,移动到src/index.ts 文件并添加以下代码:

index.ts

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

createConnection().then(async connection => { 

   console.log("Inserting a new record into the student database..."); 
   
   //create student object const stud = new Student(); 
   
   //Assign student name and age here stud.Name = "student1"; 
   stud.age = 12; 
   
    //save student object in connection await connection.manager.save(stud); console.log("Saved a new user with id: " + stud.id);
    
    console.log("Loading users from the database...");

    //Display student saved records const students = await connection.manager.find(Student); console.log("Loaded users: ", students);

    console.log("Here you can setup and run express/koa/any other framework.");
}).catch(error => console.log(error));

这里,

  • 第 1-3 行导入相关类,createConnectionStudent
  • 第 5 行使用createConnection 创建一个新的数据库连接,如果连接建立,它将在then 块中执行代码。
  • 第 10 行创建新的 Student 对象 stud。
  • 第 13-14 行设置新创建的 stud 对象的属性。

  • 第 17 行使用 **connection.manager** 对象中提供的 save 方法将实体保存到数据库。
  • 第 23 行使用 **connection.manager** 对象中提供的 find 方法从数据库中获取学生详细信息。

启动 Mysql 服务器并运行您的应用程序

我们已经创建了 Student 实体并在 index.ts 中创建了连接。让我们同时启动 MySql 服务器和您的应用程序。

npm start

这将在您的屏幕上显示以下输出 -

输出

Inserting

打开 **mysql** 服务器,以下 **student** 表已添加到您的数据库中。

Student Table

如前所述,实体实际上是属性的集合。实体对象引用数据库表。其属性/成员变量引用相应的数据库表的字段/列。TypeORM 通过 Column 类支持所有类型的数据库字段。在本章中,让我们学习 TypeORM 支持的不同类型的列。

**@Column()** 装饰器类用于表示实体中的列及其类型。

例如,学生实体的 age 属性和 age 属性的类型可以定义如下:

@Column("int") age: integer; // OR @Column({ type: "int" }) age: integer;

这里,

  • **age** 是实体的属性。换句话说,age 是数据库中 student 表中的一个字段/列。
  • **int** 表示数据库中 age 列的类型。

TypeORM 支持流行的数据库引擎中几乎所有可用的类型。实际上,TypeORM 为每个数据库引擎启用了一组不同的类型。我们可以毫无问题地使用我们的数据库引擎支持的任何数据库类型。

例如,TypeORM 对 postgresql 数据库引擎支持的类型如下:

int, int2, int4, int8, smallint, integer, bigint, decimal, numeric, real, float, float4, float8, double precision, money, character varying,

varchar, character, char, text, citext, hstore, bytea, bit, varbit, bit

varying, timetz, timestamptz, timestamp, timestamp without time zone, timestamp with time zone, date, time, time without time zone, time with time zone, interval, bool, boolean, enum, point, line, lseg, box, path, polygon, circle, cidr, inet, macaddr, tsvector, tsquery, uuid, xml, json, jsonb, int4range, int8range, numrange, tsrange, tstzrange, daterange, geometry, geography, cube

类似地,TypeORM 对 MySQL 支持一组不同的数据类型。

列选项

除了类型之外,TypeORM 还提供了一套广泛的选项来描述列。例如,length 选项指的是数据库字段的长度,可以如下指定:

@Column("varchar", { length: 100 })

一些最常见的列选项如下:

  • **name** - 数据库字段/列的名称。
  • **length** - 数据库字段/列的长度。
  • **nullable** - 指定数据库字段/列是否允许为空。
  • **default** - 数据库字段/列的默认值。
  • **primary** - 指定数据库字段/列是否是表的主键。
  • **unique** - 指定数据库字段/列是否唯一
  • **precision** - 数据库字段/列的精度
  • **scale** - 数据库字段/列的刻度
  • **comment** - 数据库字段/列的注释或描述

@Generated 装饰器

TypeORM 提供了额外的装饰器 @Generated 来自动生成列值。例如,通用唯一标识符 (UUID) 在数据库中非常常用,用于在列中存储唯一值。生成 UUID 的示例代码如下:

@Entity() 
export class Student {
 
   @PrimaryColumn() 
   id: number; 
   
   @Column() 
   @Generated("uuid") 
   uuid: string; 
}

这里,

**uuid** 是自动生成的,并存储在数据库中。

主键列

数据库中任何实体都必须至少有一个主键字段。它被分为不同类型的装饰器。我们将逐一讨论它们。

@PrimaryColumn()

@PrimaryColumn() 装饰器用于为任何类型的数据创建主键列。下面显示了一个简单的示例,

import {Entity, PrimaryColumn} from "typeorm"; 

@Entity() 
export class Student {        
@PrimaryColumn() 
   id: number; 
}

这里,

**id** 是一个整数,不接受重复值,但我们需要分配值。

如果情况需要,我们也可以为一个或多个字段分配主键列。

示例

import {Entity, PrimaryColumn} from "typeorm"; 

@Entity() 
export class Student { 
   
   @PrimaryColumn() 
   id: number; 
   
   @PrimaryColumn() 
   email: string; 
   
   @PrimaryColumn() 
   phone: number; 
}

@PrimaryGeneratedColumn()

**@PrimaryGeneratedColumn()** 字段用于指定主键列以及自动生成数据库中的列值。如下所示:

import {Entity, PrimaryGeneratedColumn} from "typeorm"; 

@Entity() 
export class Student {

   @PrimaryGeneratedColumn() 
   id: number;
}

这里,

您不必分配 id 值;它将由 TypeORM 在数据库表中自动生成。

@PrimaryGeneratedColumn(“uuid”)

@PrimaryGeneratedColumn 也接受一个参数来指定生成器的类型。主要用途之一是基于 UUID 生成唯一 ID。

import {Entity, PrimaryGeneratedColumn} from "typeorm";

@Entity() 
export class Student {  
   @PrimaryGeneratedColumn("uuid") id: string; 
}

simple-array 列类型

高级关系数据库支持数组数据类型。为了支持数组数据类型,TypeORM 提供了一种特殊的列类型“simple-array”来存储原始数组值。使用它的示例代码如下:

@Entity() 
export class Student { 
   
   @PrimaryGeneratedColumn() 
   id: number;

   @Column("simple-array") 
   names: string[]; 
}

simple-json 列类型

许多现代数据库引擎支持 JSON 数据库。要使用 JSON 数据类型,TypeORM 提供了一种特殊的类型 single-json。使用它的示例代码如下:

@Entity() 
export class Student { 

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column("simple-json")
   info: { firstName: string, middleName: string, lastName: string }; 
}

此值可以在 index.ts 中定义为:

index.ts

const stud = new Student(); 
stud.info = { firstName: "John", middleName: "peter", lastName: "Michael" };

特殊列

TypeORM 支持以下特殊列

  • **@CreateDateColumn** - 它是一个特殊的列,用于自动设置实体的插入日期。
  • **@UpdateDateColumn** - 它用于自动设置实体的更新时间。
  • **@VersionColumn** - 自动为实体设置版本号。

实体继承

实体继承用于减少实体的重复。考虑以下实体:

Result.ts

@Entity() 
export class Result {    

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   title: string; 
   
   @Column() 
   description: string; 
   
   @Column() 
   eligible: string 
}

Grade.ts

grade.ts 的代码如下:

@Entity() 
export class Grade {

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string; 
   
   @Column() 
   title: string; 
   
   @Column() 
   description: string;
   
   
   
   @Column() 
   grading : string; 
}

这里,

以上两个实体都有列 id、title 和 description。使用实体继承,我们创建一个基类 Details 并将以上两个实体组合如下。

Details.ts

export abstract class Details {

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   title: string; 
   
   @Column() 
   description: string; 
} 
@Entity() 
export class Result extends Details{  

   @Column() 
   eligible: string 
} 
@Entity() 
export class Grade extends Details{   

   @Column() 
   name : string; 
   
   @Column() 
   grading : string; 
}

现在启动您的服务器,您将看到以下响应:

TS Node

现在打开您的 mysql 服务器并转到您的数据库,您将看到以下表格:

Mysql Server

Grade 表

Grade table

Result 表

Result table

TypeORM - 关系

关系用于引用数据库中表之间的关系。通常,当其中一个表具有引用另一个表主键的外键时,这两个表之间存在关系。此功能使关系数据库功能更强大,并能有效地存储信息。

TypeORM 允许实体相互关联,并随后关联数据库表。通常,关系可以分为四类。它们如下:

**一对一** - 给定实体的一个对象仅与目标实体的一个对象相关联,反之亦然。例如,一个国家只有一个首都,同样一个城市也只在一个国家的首都。

**多对一** - 给定实体的多个对象与目标实体的一个对象相关联。例如,城市只属于一个国家,但一个国家可以有多个城市。

**一对多** - 与多对一相同,只是关系相反。

**多对多** - 给定实体的多个对象与目标实体的多个对象相关联。例如,一篇文章可能被标记在多个主题下,如编程语言、金融等,同时一个特定的标签也可能有多篇文章。

TypeORM 还提供选项来增强实体的关系。它们如下:

  • **eager** - 源实体对象也加载目标实体对象。
  • **cascade** - 当源实体对象插入或更新时,目标实体对象也会被插入或更新。
  • **onDelete** - 当源实体对象被删除时,目标实体对象也会被删除。
  • **primary** - 用于指定关系列是否为主键。
  • **nullable** - 用于指定关系列是否可为空。

让我们详细了解不同类型的关系映射。

一对一

正如我们之前了解到的,它指的是一个表字段的实例包含另一个表字段的实例,反之亦然。让我们创建一个 **Details** 表 -

Details.ts

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

@Entity() 
export class Details {
   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   gender: string; 
   
   @Column() 
   country: string; 
}

让我们再创建一个名为 Customer 的实体,如下所示:

Customer.ts

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

import {Details} from "./Details"; 

@Entity() 
export class Customer { 

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string; 
   
   @OneToOne(type => Details) @JoinColumn() 
   details: Details;
}

这里,

我们已将映射 **OneToOne** 添加到 **Details** 表。**@JoinColumn()** 包含一个“关系 ID”以及到 **Customer** 表的外键。我们可以如下在 **index.ts** 中保存关系:

const details = new Details(); details.gender = "female"; details.country = "india" await connection.manager.save(details);

const customer = new Customer(); customer.name = 'customer1'; customer.details = Details; await connection.manager.save(Customer);

一对多和多对一

正如我们之前了解到的,它指的是第一个表字段的实例包含第二个表字段的多个实例,称为 **一对多** 映射;而第一个表的多个实例只包含第二个表的一个实例,称为 **多对一** 映射。

考虑 **Student** 和 **project** 实体的示例,其中学生可以参与多个项目,但每个项目都只由一名学生负责。

让我们创建一个名为 **Project** 的实体,如下所示:

Project

import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm"; import {Student} from "./Student"; 
@Entity() 
export class Project {  

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   projects: string; 
   
   @ManyToOne(type => Student, student => student.projects) student: Student; 
}

现在,我们创建如下所示的 **Student** 实体:

import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm"; import {Project} from "./Project"; 

@Entity() 
export class User {  
   
   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string; 
   
   @OneToMany(type => Project, project => project.student) projects: Project[];  
}

这里,

**@OneToMany** 属性映射到 **Project**,而 **@ManyToOne** 属性映射到 **Student**。但是,**@OneToMany** 无法在没有 **@ManyToOne** 的情况下存在,并且 **@ManyToOne** 属性包含“关系 ID”和外键。

我们可以如下在 **index.ts** 中保存连接:

const proj1 = new Project(); proj1.projects = "database management"; await connection.manager.save(proj1); 

const proj2 = new Project(); proj2.projects = "web application"; await connection.manager.save(proj2); 

const stud = new Student(); stud.name = "Student1"; stud.projects = [proj1, proj2]; await connection.manager.save(stud);

多对多

正如我们之前了解到的,它指的是一个表中的多条记录与另一个表中的多条记录相关联。例如,大学学生可以同时选修多门课程,这意味着学生每学期可能选修四五门课程,而一门课程可以有多名学生。

我们可以简单地得出结论,一个学生可以选修多门课程,而一门课程可以有多名学生。让我们为 **Classes** 创建一个实体,如下所示:

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

@Entity() 
export class Classes { 

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string; 
}

现在,我们创建如下所示的 **Student** 实体:

import {Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable} from "typeorm"; 
import {Classes} from "./Classes";

@Entity() 
export class Student { 

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string;

   @Column() 
   subjects: string; 
   
   @ManyToMany(type => Classes) @JoinTable() 
   classes: Classes[];
}

TypeORM - 使用 Repository

Repository 专用于某个实体。换句话说,每个实体都有自己的内置存储库,并且可以使用 connection 对象的 getRepository() 方法访问它,如下所示:

const studRepository = manager.getRepository(Student);

创建 student 存储库对象后,就可以使用它来执行 student 对象的所有数据库操作。

Repository 类型

**Repository** 分为四类。它们如下:

Repository

实体的默认存储库,可以使用 **getRepository()** 方法访问它,如下所示:

const studRepository = manager.getRepository(Student);

现在,**studRepository** 可用于查询 student 表

TreeRepository

用于树状结构实体,可以使用 **getTreeRepository()** 方法访问它,如下所示:

const studcaRepository = manager.getTreeRepository(Student);

MongoRepository

用于 MongoDB 操作实体,可以使用 **getMongoRepository()** 方法访问它,如下所示:

const detailsRepository = manager.getMongoRepository(Details);

CustomRepository

用于自定义存储库,可以使用 **getCustomRepository()** 方法访问它,如下所示:

const myUserRepository = manager.getCustomRepository(UserRepository);

Repository API

让我们在本节中学习 EntityManager 中最重要的方法。

manager

我们可以使用 manager 方法访问 **EntityManager**,如下所示:

const manager = repository.manager;

queryRunner

**queryRunner** 方法返回自定义查询运行器对象,存储库使用它进行数据库操作。示例代码如下:

const queryRunner = repository.queryRunner;

metadata

**metadata** 返回存储库的元数据。示例代码如下:

const metadata = repository.metadata;

query

**query** 方法执行 SQL 查询。简单的 select 查询如下所示:

const qur = await repository.query(`select * from students`);

insert

**insert** 方法用于将新实体或实体数组插入数据库。示例代码如下:

await repository.insert({ 
   Name: "Student3", 
   Age: 14 
});

以上查询等价于:

insert into student(Name,age) values("Student3",14)

update

**update** 用于更新数据库中现有的记录。

await repository.update(1, { Name: "Adam" });

此查询的工作方式类似于下面提到的查询:

update student SET Name = "Adam" where id = 1

delete

**delete** 方法将从表中删除指定的记录,

await repository.delete(Student, 1);

这将从 **student** 表中删除 id 为 **1** 的学生。它等价于:

delete from student where id=1;

如果要按名称删除,请使用以下查询:

await repository.delete({ Name: "Student1" });

此查询将删除所有名称为 **Student1** 的学生

** softDelete 和 restore **

它用于软删除数据,您可以根据学生的 ID 恢复记录。示例代码如下:

await repository.softDelete(1);

您可以使用以下命令恢复学生记录:

await repository.restore(1);

删除和恢复的另一种选择是使用 **softRemove** 和 **recover** 方法。示例代码如下:

//find the entities const enty = await repository.find(); 

//soft removed entity const entySoftRemove = await repository.softRemove(enty);

并且,您可以使用如下所示的 recover 方法恢复它们:

await repository.recover(entySoftRemove);

save

**save** 用于将给定实体保存到数据库中。简单的 Student 实体可以如下保存:

import {Student} from "./entity/Student"; 

createConnection().then(async connection => {                     
   console.log("Inserting a new record into the student database..."); 
   const stud = new Student();
   stud.Name = "student1"; 
   stud.age = 12; 
   await repository.save(stud);

这将在数据库中添加新的学生记录。

remove

**remove** 用于从数据库中删除给定实体。简单的 Student 实体可以如下删除:

await repository.remove(stud);

count

**count** 方法将返回表中可用的记录数,您可以将其用于分页目的。示例代码如下:

const cnt = await repository.count(Student, { age: 12 });

find

find 方法用于搜索目的。它从数据库中获取所有记录,如下所示:

const result = await repository.find({ id: 1 });

findOne

类似于find 方法,但返回第一个匹配的记录。示例代码如下:

const result = await repository.findOne({ id: 1 });

clear

clear 方法清除表中的所有数据。示例代码如下:

await repository.clear();

TypeORM - 使用 Entity Manager

EntityManager 类似于Repository,用于管理数据库操作,例如插入、更新、删除和加载数据。Repository 处理单个实体,而EntityManager 对所有实体都是通用的,并且能够对所有实体执行操作。

实体管理器 API

我们可以使用getManager() 方法访问EntityManager,如下所示:

import { getManager } from "typeorm"; const entityManager = getManager();

让我们在本节中学习 EntityManager 中最重要的方法。

connection

connection 方法返回到特定数据库的数据库 ORM 连接。示例代码如下:

const connection = manager.connection;

QueryRunner

queryRunner 方法返回自定义查询运行器对象,实体管理器使用它进行数据库操作。示例代码如下:

const queryRunner = manager.queryRunner;

transaction

如果调用多个数据库请求,事务将在单个数据库事务中执行。获取事务的示例代码如下:

await manager.transaction(async manager => { 
});

query

query 方法执行 SQL 查询。简单的插入查询,如下所示:

const qur = await manager.query(`insert into student(name,age) values('stud2',13)`);

insert

**insert** 方法用于将新实体或实体数组插入数据库。示例代码如下:

await manager.insert(Student, { 
   Name: "Student3", 
   Age: 14 
});

update

**update** 用于更新数据库中现有的记录。

await manager.update(User, 1, { Name: "Adam" });

此查询与以下 SQL 查询类似:

UPDATE student SET Name = "Adam" WHERE id = 1

delete

delete 方法将删除表中指定的记录,

await manager.delete(Student, 1);

这将删除学生记录中 id 为 1 的记录。

save

**save** 用于将给定实体保存到数据库中。简单的 Student 实体可以如下保存:

import {Student} from "./entity/Student";

createConnection().then(async connection => {   
   console.log("Inserting a new record into the student database..."); 
   const stud = new Student(); stud.Name = "student1"; 
   stud.age = 12; 
   await connection.manager.save(stud); 
}

这将在数据库中添加新的学生记录。如果给定的学生在数据库中不存在,则 save 方法将插入学生。否则,save 将更新数据库中现有的学生记录。

remove

**remove** 用于从数据库中删除给定实体。简单的 Student 实体可以如下删除:

await manager.remove(stud);

count

**count** 方法将返回表中可用的记录数,您可以将其用于分页目的。示例代码如下:

const cnt = await manager.count(Student, { age: 12 });

find

find 方法用于搜索目的。它从数据库中获取所有记录,如下所示:

console.log("Loading users from the database..."); 
const students = await connection.manager.find(Student); console.log("Loaded users: ", students);

findOne

类似于find 方法,但返回第一个匹配的记录。示例代码如下:

const stud = await manager.findOne(Student, 1);

clear

clear 方法清除表中的所有数据。示例代码如下:

await manager.clear(Student);

TypeORM - 查询构建器

查询生成器用于以简单的方式构建复杂的 SQL 查询。它从 Connection 方法和 QueryRunner 对象初始化。

我们可以通过三种方式创建 QueryBuilder。

Connection

考虑一个使用 connection 方法使用 QueryBuilder 的简单示例。

import {getConnection} from "typeorm"; 

const user = await getConnection() .createQueryBuilder() 
.select("user") 
.from(User, "user") 
.where("user.id = :id", { id: 1 }) .getOne();

实体管理器

让我们创建一个使用实体管理器的查询生成器,如下所示:

import {getManager} from "typeorm"; 

const user = await getManager() .createQueryBuilder(User, "user") .where("user.id = :id", { id: 1 })    .getOne();

Repository

我们可以使用存储库来创建查询生成器。如下所述,

import {getRepository} from "typeorm"; 

const user = await getRepository(User) .createQueryBuilder("user") .where("user.id = :id", { id: 1 }) .getOne();

别名

别名与 SQL 别名相同。我们使用 QueryBuilder 为 Student 表创建别名,如下所述:

import {getConnection} from "typeorm"; 

const user = await getConnection() .createQueryBuilder() 
.select("stud") 
.from(Student, "stud")

此查询等效于:

select * from students as stud

参数

参数用作查询中动态值的占位符。在许多情况下,查找不同实体对象的查询将相同,除了值。例如,查找不同学生的查询相同,除了学生 ID 数据。在这种情况下,我们可以为学生 ID 使用参数,然后更改参数以获取不同的学生对象。

参数的另一个重要用途是防止 SQL 注入。它是现代 Web 应用程序中重要的安全漏洞之一。通过在查询中使用参数,我们可以避免 SQL 注入攻击。

参数的另一个重要用途是防止 SQL 注入。它是现代 Web 应用程序中重要的安全漏洞之一。通过在查询中使用参数,我们可以避免 SQL 注入攻击。

例如

"student.id = :id", { id: 1 }

这里,

:id - 参数名称。

{ id: 1 } - 参数的值

添加表达式

本节说明如何使用表达式。

where

where 用于过滤满足条件的记录。

createQueryBuilder("student") .where("student.id = :id", { id: 1 })

此查询等效于:

select * from students student where student.id=1;

我们也可以在其中使用 AND、OR、NOT、IN 条件。

having

简单的 having 表达式定义如下:

createQueryBuilder("student") .having("student.id = :id", { id: 1 })

此查询等效于:

select * from students student having student.id=1;

orderBy

orderby 用于根据字段对记录进行排序。

createQueryBuilder("student") .orderBy("student.name")

此查询等效于:

select * from students student order by student.name;

groupBy

它用于根据指定的列对记录进行分组。

createQueryBuilder("student") .groupBy("student.id")

此查询等效于:

select * from students student group by student.id;

limit

它用于限制行的选择。下面,示例显示了如何在查询生成器中使用 limit,

createQueryBuilder("student") .limit(5)

此查询等效于:

select * from students student limit 5;

offset

Offset 用于指定跳过结果多少行。它定义如下:

createQueryBuilder("student") .offset(5)

此查询等效于:

select * from students student offset 5;

joins

join 子句用于根据相关列组合来自两个或多个表的行。考虑两个实体:

Student.ts

import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm"; 
import {Project} from "./Project"; 

@Entity() 
export class User {
   
   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string; 
   
   @OneToMany(type => Project, project => project.student) projects: project[]; 
}

Project.ts

import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm"; 
import {Student} from "./Student"; 

@Entity() 
export class Project { 

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   title: string; 
   
   @ManyToOne(type => Student, student => student.projects) student: Student; 
}

让我们使用以下查询执行简单的左连接:

const student = await createQueryBuilder("student") .leftJoinAndSelect("student.projects", "project") 
.where("student.name = :name", { name: "Student1" }) 
.getOne();

此查询等效于:

SELECT student.*, project.* FROM students student 
   LEFT JOIN projects project ON project.student = student.id 
   WHERE student.name = 'Student1'

类似地,我们也可以尝试内部连接。

不带选择的连接

我们可以不使用 select 连接数据。让我们尝试使用内部连接的以下示例:

const student = await createQueryBuilder("student") .innerJoin("student.projects", "project") 
   .where("student.name = :name", { name: "student1" }) 
   .getOne();

以上查询等效于:

SELECT student.* FROM students student 
   INNER JOIN projects project ON project.student = student.id 
   WHERE student.name = 'Student1';

分页

如果您的应用程序中有更多数据,则需要分页、页面滑块或滚动功能。

例如,如果您想在您的应用程序中显示前五个学生的项目,

const students = await getRepository(Student) .createQueryBuilder("student") .leftJoinAndSelect("student.projects", "project") 
   .take(5) 
   .getMany();

子查询

称为另一个查询或嵌套查询中的查询。我们在 FROM、WHERE 和 JOIN 表达式中使用子查询。

简单的示例如下所示:

const projects = await connection .createQueryBuilder() .select("project.id", "id")
.addSelect(subQuery => { 
   return subQuery 
      .select("student.name", "name") .from(Student, "student") 
      .limit(1); 
}, "name")
.from(Project, "project") .getMany();

隐藏字段

如果您的任何列字段标记为 {select: false},则该列被视为隐藏列。考虑以下实体:

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

@Entity() 
export class Student {

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string; 
   
   @Column({select: false}) 
   address: string; 
}

这里,

address 字段标记为隐藏。我们可以使用addSelect 方法从列中检索信息。它定义如下,

const student = await connection.getRepository(Student) .createQueryBuilder() .select("student.id", "student")    .addSelect("student.address") .getMany();

getSql()

此方法用于获取查询生成器生成的 SQL 查询。它定义如下:

const sql = createQueryBuilder("student") .where("student.name = :name", { name: "Student1" })  .orWhere("student.age = :age", { age: 14 }) 
.getSql();

TypeORM - 查询操作

数据操作用于管理和查看数据。本节说明如何使用 QueryBuilder 访问数据库查询,如插入、更新、选择和删除查询。让我们详细了解一下。

构建插入查询

让我们创建一个Customer 实体,如下所示:

Customer.ts

import {Entity, PrimaryGeneratedColumn, Column} from "typeorm"; 
@Entity() 
export class Customer {       

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   name: string; 
   
   @Column() 
   age: number; 
}

让我们在 index.ts 中添加以下更改,如下所示:

index.ts

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

createConnection().then(async connection => { 
   await getConnection().createQueryBuilder()   .insert() 
      .into(Customer)  
      .values([ { name: "Adam",age:11}, 
         { name: "David",age:12} ]) .execute(); 
}).catch(error => console.log(error));

现在,使用以下命令启动您的应用程序:

npm start

输出

您可以在屏幕上看到以下输出:

Data Inserted

现在打开您的 mysql 服务器,表插入了两个字段,如下所示:

Table Inserted

构建更新查询

上一节,我们插入了两行数据。让我们检查更新查询如何工作。在 index.ts 中添加以下更改,如下所示:

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

createConnection().then(async connection => { 

await getConnection()         
   .createQueryBuilder() .update(Customer) 
   .set({ name: "Michael" }) .where("id = :id", { id: 1 }) .execute(); 
   console.log("data updated"); 
   
}).catch(error => console.log(error));

现在,使用以下命令启动您的应用程序:

npm start

您可以在屏幕上看到以下输出:

Data Updated

Mysql 表修改如下所示:

Mysql Table

构建选择查询

select 查询用于显示表中的记录。让我们在index.ts 中添加以下代码,如下所示:

index.ts

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

createConnection().then(async connection => { 

   console.log("Display records from Customer table..."); 
   const cus = new Customer();

   console.log("Loading customers from the database..."); 
   const customers = await connection.manager.find(Customer); console.log("Loaded users: ", customers); 
}).catch(error => console.log(error));

您可以在屏幕上看到以下输出:

Output

where 表达式

让我们在查询中添加 where 表达式以过滤客户。示例代码如下:

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

createConnection().then(async connection => { 
   const customer = await getConnection() .createQueryBuilder() .select("cus") 
   .from(Customer, "cus") .where("cus.id = :id", { id: 1 }) .getOne(); 
   
   console.log(customer); 
})
.catch(error => console.log(error));

以上程序将返回第一个 id 记录。您可以在屏幕上看到以下输出,

First Id Records

类似地,您也可以尝试其他表达式。

构建删除查询

上一节,我们已经插入、更新和选择数据。让我们检查删除查询如何工作。在 index.ts 中添加以下更改,如下所示:

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

createConnection().then(async connection => { 
   await getConnection() .createQueryBuilder() 
   .delete() 
   .from(Customer) 
   .where("id = :id", { id: 1 }) .execute();
console.log("data deleted"); }).catch(error => console.log(error));

您可以在屏幕上看到以下输出:

Output on Screen

您的 mysql 表修改如下:

Mysql Table is Modified

TypeORM - 事务

通常,事务是一个逻辑单元,负责执行数据检索和更新。本节详细说明事务。

创建事务

我们可以使用连接或 EntityManage 创建事务。以下示例用于指定创建连接并在其中保存数据。

import {getConnection} from "typeorm"; 

await getConnection().transaction(async transactionalEntityManager => { 

   await connection.manager.save(students); 

});

EntityManager 如下所示:

import {getManager} from "typeorm";

await getManager().transaction(async transactionalEntityManager => { 
   await transactionalEntityManager.save(students); 
});

装饰器

在 TypeORM 中,我们有三种与事务相关的装饰器。

  • @Transaction - 将所有执行包装在单个数据库事务中。
  • @TransactionManager - 用于在事务中执行查询。它定义如下,
@Transaction({ isolation: "SERIALIZABLE" }) 

save(@TransactionManager() manager: EntityManager, student: Student) {     
   return manager.save(student); 
}

这里,

我们使用SERIALIZABLE 隔离级别进行事务。

  • @TransactionRepository - 用于在存储库中注入事务。它定义如下,
@Transaction() save(student: Student, @TransactionRepository(Student) studentRepository: 
Repository<Student>) { 
   return studentRepository.save(student); 
}

QueryRunner 中的事务

QueryRunner 用于执行所有数据库查询。它具有单个数据库连接。可以使用QueryRunner组织数据库事务。让我们使用QueryRunner执行单个事务。

import {getConnection} from "typeorm"; 

// get a connection and create a new query runner 
const connection = getConnection(); const queryRunner = connection.createQueryRunner(); 

// establish real database connection using our new query runner 
await queryRunner.connect(); 

// now we can execute any queries on a query runner, for example: await queryRunner.query("SELECT * FROM students");

现在,使用以下语句启动事务:

await queryRunner.startTransaction();

然后,使用以下语句提交和回滚事务,

try { 
   await queryRunner.commitTransaction(); 
}

如果出现任何错误,则由 catch() 处理,

catch (err) { 

   // since we have errors lets rollback changes we made await queryRunner.rollbackTransaction(); 
}

现在,释放 queryRunner,如下所示:

finally { 
   
   // you need to release query runner which is manually created: await queryRunner.release(); 
}

TypeORM - 索引

通常,索引是一个通过优化数据存储来优化数据库性能的过程。它用于快速定位和访问数据库中的数据。本节说明如何在 TypeORM 中使用索引。索引分为不同类型。让我们详细了解一下。

列索引

我们可以使用@Index为特定列创建索引。考虑以下所示的Customer 实体示例以及为firstName 列定义的索引,

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

@Entity() 
export class Student { 

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Index() 
   @Column() 
   firstName: string; 
   
   @Column() 
   lastName: string; 
   
   @Column() 
   age: number; 
   
   @Column() 
   address: string; 
}

@Index 也允许为索引指定名称:

@Index("Name-idx") 
@Column() 
firstName: string;

唯一索引

要在您的列中指定唯一约束,请使用以下属性:

{ unique: true }

例如,以下是为 Name 列指定唯一索引的代码:

@Index({ unique: true }) 
@Column() 
firstName: string;

要为多列应用索引,我们可以直接在 @Entity() 后指定它。示例代码如下:

@Entity() 
@Index(["firstName", "lastName"]) @Index(["firstName", "lastName"], { unique: true })

空间索引

空间索引允许访问空间对象。MySQL 和 PostgreSQL 支持空间索引。要在您的列中启用空间索引,请添加以下属性:

{ spatial: true }

空间类型有多个子类型,例如几何、点、线字符串、多边形等。例如,如果要在您的列中添加点空间类型,请使用以下代码:

@Column("point") 
@Index({ spatial: true }) 
point: string;

禁用同步

要禁用同步,请在@Index 装饰器上使用以下选项:

{ synchronize: false }

TypeORM - 实体监听器和日志记录

实体侦听器用于支持自定义方法并侦听特定事件的实体。我们可以使用装饰器定义任何实体自定义方法。让我们简要了解一下装饰器。

  • @AfterLoad - 当使用 QueryBuilder 或存储库/管理器加载实体时,将调用此方法。
  • @BeforeInsert - 使用存储库/管理器插入实体之前,将调用此方法。
  • @AfterInsert - 使用存储库/管理器插入实体后,将调用此方法。
  • @BeforeUpdate - 使用存储库/管理器更新现有实体之前,将调用此方法。
  • @AfterUpdate - 更新实体后,将调用它。
  • @BeforeRemove - 删除实体之前,将调用它。
  • @AfterRemove - 删除实体后,将调用它。

订阅者

订阅者用于侦听特定实体事件。它从EntitySubscriberInterface实现。让我们了解一个关于如何在订阅者中使用实体侦听器的简单示例。考虑以下所示的学生实体:

Student.ts

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

@Entity() 
export class Student {     

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   Name: string; 
   
   @Column() 
   age: number; 
}

创建学生订阅者

订阅者使用以下命令创建:

typeorm subscriber:create -n StudentSubscriber

以上命令在您的项目 src 中创建一个订阅者目录。然后,在您的订阅者中创建 StudentSubscriber.ts 文件。您可以看到以下响应,

Subscriber /Users/workspace/TypeORM/FirstProject/src/subscriber/StudentSubscriber.ts has been created successfully.

现在转到文件,您可以看到以下代码:

StudentSubscriber.ts

import {EventSubscriber, EntitySubscriberInterface} from "typeorm"; 

@EventSubscriber() 
export class StudentSubscriber implements EntitySubscriberInterface<any> { 
}

现在,在文件中添加以下更改,

import {EventSubscriber, EntitySubscriberInterface,InsertEvent} from "typeorm"; 
import {Student} from "../entity/Student"; 

@EventSubscriber() 
export class StudentSubscriber implements EntitySubscriberInterface<any> { 
   listenTo() 
   { 
      return Student; 
   } 
   
   afterInsert(event: InsertEvent<Student>) { 
      console.log(event); 
   } 
}

这里,

我们已使用afterInsert() 方法调用实体事件。类似地,您也可以使用其他事件。我们已经配置了 ormconfig.json 文件。现在,在index.ts 文件中添加以下更改,如下所示:

index.ts

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

createConnection().then(async connection => {

   console.log('connection established'); 
}).catch(error => console.log(error));

执行应用程序后,您可以在屏幕上看到以下输出,

Application

日志记录

数据库日志记录是您的高可用数据库解决方案设计的重要组成部分,因为数据库日志使从故障中恢复成为可能,并且使同步主数据库和辅助数据库成为可能。

所有数据库都与其关联的日志相关联。这些日志记录数据库更改的记录。如果需要将数据库恢复到最后一个完整脱机备份之前的某个点,则需要日志才能将数据向前滚动到故障点。

日志记录选项

通过在数据库连接中添加 {logging: true} 来启用日志记录。日志记录选项分为不同类型。它们如下:

query - 返回所有日志查询。它定义如下:

{ 
   host: "localhost",
   ... 
   logging: ["query"] 
}

error - 返回所有失败查询和错误的日志。它定义如下:

{ 
   host: "localhost",
   ... 
   logging: ["error"] 
}

schema - 返回模式的日志。

warn - 返回内部 ORM 警告。

info - 返回内部 ORM 信息性消息的日志。

log − 返回内部 ORM 日志消息。

自定义日志记录器

自定义日志记录是一个简单且高度可定制的日志记录选项。我们可以使用以下代码创建自己的日志记录器类:

import {Logger} from "typeorm"; 

export class MyCustomLogger implements Logger { 
   
   // implement all methods from logger class 
}

连接选项在 ormconfig.json 中指定,如下所示:

name: "mysql", 
type: "mysql", 
host: "localhost", 
port: 3306, 
username: "root", 
password: "root", 
database: "test", 
logger: new MyCustomLogger()

TypeORM 与 JavaScript

TypeORM 默认支持的语言是 TypeScript。由于 TypeScript 支持静态类型、类和装饰器,因此定义实体及其属性非常容易。同时,JavaScript 在某些首选语言为 JavaScript 的项目中也是必要的。TypeORM 也完全支持 JavaScript 语言。TypeORM 支持 JavaScript 的 es5 和 es6 版本。

在本章中,让我们学习如何在 JavaScript ES5(ECMAScript 5)中编写 TypeORM 应用程序。

打开命令提示符并转到您的工作区。

cd /path/to/workspace/

运行以下命令创建 TypeORM 项目。

typeorm init --name typeorm-javascript-student-app --database mysql

打开 package.json 文件以删除 typescript 引用。

原始

{ 
   "name": "typeorm-javascript-student-app", "version": "0.0.1", 
   "description": "Awesome project developed with TypeORM.", "devDependencies": { 
      "ts-node": "3.3.0", "@types/node": "^8.0.29", "typescript": "3.3.3333" 
   }, 
   "dependencies": { 
      "typeorm": "0.2.24", "reflect-metadata": "^0.1.10", "mysql": "^2.14.1" 
   }, 
   "scripts": { 
      "start": "ts-node src/index.ts" } 
}

更新

{ 
   "name": "typeorm-javascript-student-app", "version": "0.0.1", 
   "description": "Awesome project developed with TypeORM.", "dependencies": { 
      "typeorm": "0.2.24",
      "mysql": "^2.14.1" 
   }, 
   "scripts": { 
   "start": "node src/index.js" 
   } 
}

这里,

  • 删除了 devDependencies 部分和 dependences 部分中与 typescript 相关的包。
  • 将启动脚本更改为指向 javascript 代码而不是 typescript 代码。

运行以下命令安装必要的包。

npm install

删除 tsconfig.json 和 index.ts 文件。

删除 entity 文件夹内的 User.ts 文件,然后创建 JSON 格式的学生实体 student.json,如下所示:

{ 
   "name": "Student", 
   "columns": { 
      "id": { 
         "primary": true, 
         "type": "int", 
         "generated": true 
      }, 
      "name": { 
         "type": "varchar" 
      }, 
      "age": { 
         "type": "integer" 
      } 
   } 
}

创建一个新文件 src/index.js 并放入以下代码:

var typeorm = require("typeorm"); var EntitySchema = typeorm.EntitySchema; 

typeorm.createConnection({ 
   "type": "mysql", 
   "host": "localhost", 
   "port": 3306, 
   "username": "root", 
   "password": "123456", 
   "database": "typeorm_test_db",
   "synchronize": true, 
   "logging": false, 
   entities: [ new EntitySchema(require("./entity/student.json")) 
   ] 
}) 
.then(function(connection) { 
   return connection.getRepository("Student"); }) .then(function(studentRepository) { 
   var student = { 
      name: "Student1", 
      age: 18 
   }; 
   return studentRepository.save(student) .then(function(savedStudent) { console.log("Student has been successfully saved: ", savedStudent); 
   return studentRepository.find(); }) 
   .then(function(students) { console.log("All students: ", students); 
   return; 
   }) 
   .catch(function(error) { console.log("Error: ", error); return; 
   }) 
}) 
.catch(function(error) { console.log("Error: ", error) 
   return; });

这里,

我们使用了相同的 typeORM 方法,除了以下提到的更改:

  • 使用 EntitySchema 配置学生实体。
  • 使用 JavaScript Promise 概念(then / catch / finally)块。

现在,使用以下命令运行应用程序:

npm start

应用程序将学生信息插入数据库,然后获取数据库中的所有学生并在控制台中显示,如下所示:

> typeorm-javascript-student-app@0.0.1 start /path/to/workspace/typeorm-javascript-student-app

> node src/index.js 

Student has been successfully saved: { name: 'Student1', age: 18, id: 1 } All students: [ { id: 1, name: 'Student1', age: 18 } ]

TypeORM - 使用 MongoDB

本章介绍了 TypeORM 提供的广泛的 MongoDB 数据库支持。希望我们已经使用 npm 安装了 mongodb。如果未安装,请使用以下命令安装 MongoDB 驱动程序:

npm install mongodb --save

创建项目

让我们使用 MongoDB 创建一个新项目,如下所示:

typeorm init --name MyProject --database mongodb

配置 ormconfig.json

让我们在 ormconfig.json 文件中配置 MongoDB 主机、端口和数据库选项,如下所示:

ormconfig.json

{ 
   "type": "mongodb", 
   "host": "localhost", 
   "port": 27017, 
   "database": "test", 
   "synchronize": true, 
   "logging": false, 
   "entities": [ 
      "src/entity/**/*.ts" 
   ], 
   "migrations": [ "src/migration/**/*.ts" 
   ], 
   "subscribers": [ "src/subscriber/**/*.ts" 
   ], 
   "cli": { 
      "entitiesDir": "src/entity", "migrationsDir": "src/migration", "subscribersDir": "src/subscriber" 
   } 
}

定义实体和列

让我们在您的 src 目录中创建一个名为 Student 的新实体。实体和列相同。要生成主键列,我们使用 @PrimaryColumn

@PrimaryGeneratedColumn。这可以定义为 @ObjectIdColumn。简单的示例如下所示:

Student.ts

import {Entity, ObjectID, ObjectIdColumn, Column} from "typeorm"; 

@Entity() 
export class Student {  

   @ObjectIdColumn() 
   id: ObjectID; 
   
   @Column() 
   Name: string; 
   
   @Column() 
   Country: string; 
}

要保存此实体,请打开 index.ts 文件并添加以下更改:

index.ts

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

createConnection().then(async connection => { 

   console.log("Inserting a new Student into the database..."); const std = new Student(); std.Name = "Student1"; 
   std.Country = "India"; 
   await connection.manager.save(std); console.log("Saved a new user with id: " + std.id); 
   
   console.log("Loading users from the database..."); 
   const stds = await connection.manager.find(Student); console.log("Loaded users: ", stds); 
   
   console.log("TypeORM with MongoDB"); 
}).catch(error => console.log(error));

现在,启动您的服务器,您将获得以下响应:

npm start
Start Server

MongoDB EntityManager

我们还可以使用 EntityManager 获取数据。简单的示例如下所示:

import {getManager} from "typeorm";

const manager = getManager(); 
const result = await manager.findOne(Student, { id:1 });

类似地,我们还可以使用存储库访问数据。

import {getMongoRepository} from "typeorm"; 

const studentRepository = getMongoRepository(Student); 
const result = await studentRepository.findOne({ id:1 });

如果要使用以下等号选项过滤数据:

import {getMongoRepository} from "typeorm"; 

const studentRepository = getMongoRepository(Student); 
const result = await studentRepository.find({ 
   where: { 
      Name: {$eq: "Student1"}, 
   } 
});

正如我们在本章中看到的,TypeORM 使使用 MongoDB 数据库引擎变得容易。

TypeORM 与 Express

Express 是一个流行的 JavaScript 框架,用于创建 Web 应用程序。让我们在本章中学习如何在 TypeORM 以及 express 框架中使用它。

创建简单的应用程序

TypeORM CLI 提供了一个简单的选项来创建一个与 TypeORM 集成的完整的工作 express Web 应用程序(Restful API 应用程序)。创建应用程序的 CLI 命令如下所示:

cd /path/to/workspace typeorm init --express --name typeorm-express-sample --database mysql

上述命令将在 typeorm-express-sample 文件夹下创建一个新的 Web 应用程序。应用程序的结构如下所示:

 .gitignore 
 ormconfig.json 
 package.json 
 README.md 
 tsconfig.json 
 └───src 
       index.ts 
       routes.ts 
       
      ├───controller 
            UserController.ts 
       
      ├───entity 
            User.ts 
       
      └───migration

这里,

众所周知,ormconfig.jsonTypeORM 配置文件。代码如下:

{ 
   "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" 
   ], 
   "cli": { 
      "entitiesDir": "src/entity", "migrationsDir": "src/migration", "subscribersDir": "src/subscriber" 
   } 
}

在此处,更改数据库设置以匹配您的本地数据库设置。

package.json 文件是应用程序的主要配置。

tsconfig.json 文件包含与 TypeScript 相关的配置。

entity 文件夹包含 TypeORM 模型。CLI 将创建一个默认的 User 模型,如下所示:

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

@Entity() 
export class User { 
   
   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   firstName: string; 
   
   @Column() 
   lastName: string; 
   
   @Column() 
   age: number; 
}

controller 文件夹包含 express 控制器。CLI 创建了一个默认的用户 API 控制器,其中包含添加/列出/删除用户详细信息。代码如下:

import {getRepository} from "typeorm"; import {NextFunction, Request, Response} from "express"; import {User} from "../entity/User"; 

export class UserController {

   private userRepository = getRepository(User); 
   
   async all(request: Request, response: Response, next: NextFunction) { 
      return this.userRepository.find(); 
   } 
   
   async one(request: Request, response: Response, next: NextFunction) { 
      return this.userRepository.findOne(request.params.id); 
   } 
   
   async save(request: Request, response: Response, next: NextFunction) { 
      return this.userRepository.save(request.body); 
   } 
   
   async remove(request: Request, response: Response, next: NextFunction) { 
      let userToRemove = await this.userRepository.findOne(request.params.id); 
      await this.userRepository.remove(userToRemove); 
   } 
}

这里,

all 方法用于从数据库中获取所有用户。

one 方法用于使用 用户 ID 从数据库中获取单个用户。

save 方法用于将用户信息保存到数据库中。

delete 方法用于使用 用户 ID 从数据库中删除用户。

routes.ts 文件将用户控制器方法映射到正确的 URL,代码如下:

import {UserController} from "./controller/UserController"; 

export const Routes = [{ 
      method: "get", 
      route: "/users", 
      controller: UserController, action: "all" 
   }, { 
      method: "get", 
      route: "/users/:id", controller: UserController, action: "one" 
   }, { 
      method: "post", 
      route: "/users", 
      controller: UserController, action: "save" 
   }, { 
      method: "delete", route: "/users/:id", controller: UserController,
      action: "remove" 
}];

这里,

/users url 映射到用户控制器。每个动词 post、get 和 delete 都映射到不同的方法。

最后,index.ts 是我们的主要 Web 应用程序入口点。源代码如下所示:

import "reflect-metadata"; 
import {createConnection} from "typeorm"; 
import * as express from "express"; import * as bodyParser from "body-parser"; 
import {Request, Response} from "express"; 
import {Routes} from "./routes"; import {User} from "./entity/User"; 

createConnection().then(async connection => { 

   // create express app const app = express(); app.use(bodyParser.json()); 

   // register express routes from defined application routes Routes.forEach(route => { 
      (app as any)[route.method](route.route, (req:   Request, res: Response, next: Function) => { 
         const result = (new (route.controller as any))[route.action](req, res, next); 
         if (result instanceof Promise) { 
            result.then(result => result !== null && result !== undefined ? res.send(result) : undefined); 
         } else if (result !== null && result !== undefined) { 
            .json(result); 
         } 
      }); 
   }); 
      
   // setup express app here 
   // ... 
      
   // start express server app.listen(3000); 
      
   // insert new users for test await connection.manager.save(connection.manager.create(User, { 
      firstName: "Timber",
      lastName: "Saw", 
      age: 27 
   }));
   await connection.manager.save(connection.manager.create(User, { 
      firstName: "Phantom", 
      lastName: "Assassin", 
      age: 24 
   })); 
      
   console.log("Express server has started on port 3000. Open https://127.0.0.1:3000/users to see results"); 
}).catch(error => console.log(error));

在这里,应用程序配置路由,插入两个用户,然后在端口 3000 上启动 Web 应用程序。我们可以在 https://127.0.0.1:3000 访问应用程序。

要运行应用程序,请按照以下步骤操作:

让我们使用以下命令安装必要的包:

npm install

输出

npm notice created a lockfile as package-lock.json. You should commit this file. 
npm WARN typeorm-express-sample@0.0.1 No repository field. 
npm WARN typeorm-express-sample@0.0.1 No license field. 

added 176 packages from 472 contributors and audited 351 packages in 11.965s 

3 packages are looking for funding  run `npm fund` for details 

found 0 vulnerabilities

运行以下命令启动应用程序。

npm start

输出

> typeorm-express-sample@0.0.1 start /path/to/workspace/typeorm-express-sample 
> ts-node src/index.ts 

Express server has started on port 3000. Open https://127.0.0.1:3000/users to see results

让我们使用 curl 命令访问我们的 Web 应用程序 API,如下所示:

curl https://127.0.0.1:3000/users

这里,

curl 是一个命令行应用程序,用于从命令提示符访问 Web 应用程序。它支持所有 HTTP 动词,例如 get、post、delete 等。

输出

[{"id":1,"firstName":"Timber","lastName":"Saw","age":27},{"id":2,"firstName":"Phantom","lastName":"Assassin","age":24}]

要获取第一条记录,我们可以使用以下命令:

curl https://127.0.0.1:3000/users/1

输出

{"id":1,"firstName":"Timber","lastName":"Saw","age":27}

要删除用户记录,我们可以使用以下命令:

curl -X DELETE https://127.0.0.1:3000/users/1

正如我们在本章中看到的,TypeORM 可以轻松集成到 express 应用程序中。

TypeORM - 迁移

迁移就像数据库的版本控制。它用于修改和共享应用程序的数据库模式。本节说明 TypeORM 中迁移的工作原理。

创建新的迁移

要创建新的迁移,首先我们需要在 ormconfig.json 中设置连接。它定义如下:

ormconfig.json

"type": "mysql", 
"host": "localhost", 
"port": 8889, 
"username": "root", 
"password": "root", 
"database": "Library", 
"entities": ["entity/*.js"], "migrationsTableName": "student_migration_table", "migrations": ["migration/*.js"], "cli": { 
   "migrationsDir": "migration" 
}

这里,

  • migrationsTableName − 它指的是迁移表名。
  • migrations − TypeORM 从给定目录加载迁移。
  • cli − 表示迁移将在特定目录内创建。

创建 Book 实体

让我们在 src/entity/Book.ts 中创建一个名为 Book 的实体,如下所示:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 
@Entity() 
export class Book { 

   @PrimaryGeneratedColumn() 
   id: number; 
   
   @Column() 
   title: string;
   
   @Column() 
   text: string; 
}

执行 CLI 以创建新的迁移

现在,我们可以使用 CLI 执行新的迁移,如下所示:

语法

typeorm migration:create -n <migration-name>

示例

typeorm migration:create -n myMigration

执行上述命令后,您将看到以下响应:

Migration /path/to/project/src/migration/1587101104904-myMigration.ts has been generated successfully.

现在,进入 src/migration/1587101104904-myMigration.ts 文件,它看起来类似于此。

import {MigrationInterface, QueryRunner} from "typeorm"; 

export class myMigration1587101104904 implements MigrationInterface {      

   public async up(queryRunner: QueryRunner): Promise<any> { 
   } 
   
   public async down(queryRunner: QueryRunner): Promise<any> { 
   } 
}

这里,

我们有两个方法 updownup 方法用于将更改添加到迁移,down 方法用于撤消迁移中的更改。

让我们在 myMigration.ts 文件中添加 up 方法,如下所示:

import {MigrationInterface, QueryRunner} from "typeorm"; 

export class Book1587131893261 implements MigrationInterface { 

   public async up(queryRunner: QueryRunner): Promise<any> { 
      await queryRunner.query(`ALTER TABLE book ADD COLUMN price int`); 
   }
   public async down(queryRunner: QueryRunner): Promise<any> { } 
}

这里,

我们在 book 表中添加了一个新的列 price。现在,执行 CLI 以添加上述更改。

ts-node ./node_modules/typeorm/cli.js migration:run

上述命令执行迁移并按顺序运行它们。现在,您可以在屏幕上看到以下更改:

输出

Executes Migrations

现在打开您的 mysql 服务器,已添加新列。如下所示:

Open Mysql Server

类似地,我们可以将列标题数据类型修改为 varchar(30),如下所示:

import {MigrationInterface, QueryRunner} from "typeorm"; 

export class Book1587131893261 implements MigrationInterface { 

   public async up(queryRunner: QueryRunner): Promise<any> { 
   await queryRunner.query(`ALTER TABLE book MODIFY COLUMN title varchar(30)`); 
      } 
   public async down(queryRunner: QueryRunner): Promise<any> { 
   } 
}

现在,执行相同的命令,您将看到以下更改:

ts-node ./node_modules/typeorm/cli.js migration:run

输出

Command

Book 表已修改为:

Book Table

撤消迁移

让我们在 down 方法中添加以下代码以撤消迁移:

import {MigrationInterface, QueryRunner} from "typeorm"; 

export class Book1587131893261 implements MigrationInterface { 
   
   public async up(queryRunner: QueryRunner): Promise<any> { 
   
   } 
   public async down(queryRunner: QueryRunner): Promise<any> { 
   
      await queryRunner.query(`ALTER TABLE book drop column price`); // reverts things made in "up" method
   } 
}

现在,执行以下命令以撤消所有更改:

ts-node ./node_modules/typeorm/cli.js migration:revert

您可以看到以下响应:

输出

Response

Book 表已修改为:

输出

Database Migration Script

正如我们在本章中看到的,TypeORM 使编写数据库迁移脚本变得容易。

TypeORM - 使用 CLI

本节详细介绍 TypeORM CLI 命令。

创建 TypeORM 项目

typeorm init 是设置 TypeORM 项目最简单、最快捷的方法。您可以创建一个新项目,如下所示:

typeorm init --name Demoproject --database mysql

执行命令后,您将在屏幕上看到以下输出:

Project created inside /Users/workspace/TypeORM/Demoproject directory.

创建实体

要使用 CLI 创建新的实体,如下所示:

typeorm entity:create -n Person

现在,Person 实体已创建在您的项目 src 目录中。

Entity /Users/workspace/TypeORM/Demoproject/src/entity/Person.ts has been created successfully.

如果您有一个具有多个实体的不同目录的多模块项目结构,您可以使用以下命令:

typeorm entity:create -n Person -d src/Person/entity

创建新的订阅者

要使用 CLI 创建新的订阅者,如下所示:

typeorm subscriber:create -n PersonSubscriber

您可以看到以下响应:

Subscriber /path/to/TypeORM/Demoproject/src/subscriber/PersonSubscriber.ts has been created successfully.

创建迁移

您可以使用以下提到的 CLI 创建新的迁移:

typeorm migration:create -n PersonMigration

上述命令在您的项目 src 中创建了一个迁移目录。迁移文件存储在其中。

Migration /path/to/TypeORM/Demoproject/src/migration/1587395030750-PersonMigration.ts has been generated successfully.

数据库模式

要同步数据库模式,请使用以下命令:

typeorm schema:sync

要完全删除数据库模式,请使用以下命令:

typeorm schema:drop

SQL 查询

如果要执行任何 sql 查询,我们可以直接从此处执行。例如,要显示客户的所有记录,请使用以下查询:

typeorm query "select * from customers"

如果要清除缓存中存储的所有内容。您可以使用以下命令执行此操作:

typeorm cache:clear

结论

TypeORM 是一个优秀的开源 ORM 框架,用于从小型应用程序到具有多个数据库的大型企业应用程序创建高质量且可扩展的应用程序。

广告