Note
TypeORM
TypeORM介绍和基本使用
TypeORM
ORM,Object Relational Mapping,对象关系映射。把“数据库世界”映射成“面向对象世界”,方便服务端操作数据库。如果不使用 ORM , 需要自己处理和数据相关的操作,如:
想查一个用户,传统方式可能需要写:
SELECT * FROM user WHERE id = 1;
Node.js 里可能变成:
const [rows] = await connection.query( ‘SELECT * FROM user WHERE id = ?’, [1], );
随着项目越来越复杂,不断地写SQL语句并且需要自己处理如SQL参数、数据转换、表关系、事务等等,会比较麻烦,所以就诞生了 ORM 。TypeORM 是 TypeScript/JavaScript 领域的 ORM 框架,负责 ORM 、SQL 生成、关系映射、事务等数据库操作。
TypeORM的映射关系
数据库
user 表
id username age
1 Tom 20
2 Jack 25
TypeORM
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
age: number;
}
形成的映射关系
| 数据库 | TypeORM |
|---|---|
| 表 | Entity 类 |
| 一行数据 | Entity 对象 |
| 字段 | 类属性 |
| 主键 | @PrimaryGeneratedColumn() |
| 外键 | Relation |
| SELECT | find() |
| INSERT | save() / insert() |
| UPDATE | save() / update() |
| DELETE | delete() / remove() |
TypeORM核心对象
| 名称 | 作用 |
|---|---|
DataSource |
管理数据库连接 |
Entity |
描述数据库表 |
Repository |
操作某一个 Entity |
EntityManager |
通用数据库操作入口 |
QueryBuilder |
构建复杂 SQL |
QueryRunner |
管理单独连接/事务 |
相关库说明
| 包 | 作用 |
|---|---|
typeorm |
ORM 本体 |
@nestjs/typeorm |
NestJS 与 TypeORM 的集成 |
DataSource
TypeORM 与数据库之间的连接入口。它里面保存数据库类型、服务器地址、端口、用户名密码、数据库名称、Entity、连接池等等。
如果不使用 NestJS ,纯 TypeORM ,通过以下方式连接:
const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "123456",
database: "test",
entities: [User],
});
await dataSource.initialize();
但是 NestJS 项目里通常不需要自己调用 new DataSource().initialize(),通过 TypeOrmModule.forRoot() 来实现:
// app.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "123456",
database: "nestjs_test",
autoLoadEntities: true,
synchronize: true,
}),
],
})
export class AppModule {}
参数
| 名称 | 含义 | 类型 |
|---|---|---|
type |
string |
数据库类 |
host |
string |
数据库服务器 |
port |
number |
端口 |
username |
string |
数据库账号 |
password |
string |
数据库密码 |
database |
boolean |
数据库名 |
entities |
array |
连接的实体。如entities: [User,Product,Order,Category] |
autoLoadEntities |
string |
自动加载通过 TypeOrmModule.forFeature() 注册的 Entity 。如果不开启,需要手动使用 entities 来维护 Entity 。 |
synchronize |
boolean |
数据库根据 Entity 自动同步数据库表结构。如在注册实体后,会自动创建对应的数据库表,实体字段修改后自动修改对应的表字段 |
synchronize 说明:
初始:
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number;
}
增加字段:
@Column()
username: string;
在开启 synchronize 时,TypeORM 可以自动修改数据库表,所以数据库的User表也会增加username字段。开发环境中可以开启,但是生产环境自动同步可能导致数据风险,正式环境应该使用 Migration 。
Entity
告诉 NestJS 这个类是数据库 Entity ,用这个类描述数据库表结构。
表示User类是数据库 Entity
// user.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
@Entity("users")
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: "varchar",
length: 50,
unique: true,
})
username: string;
@Column({
type: "int",
nullable: true,
})
age: number | null;
@Column({
default: true,
})
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
默认情况下类名会映射成表名,也可以显示指定数据库表明。推荐显示指定
// 表示:
// TypeScript 类:User
// 数据库表:users
@Entity("users")
export class User {}
@PrimaryGeneratedColumn()
自动生成的主键。还有一种 @PrimaryColumn() ,表示主键值需要自己提供,通常使用 @PrimaryGeneratedColumn() 。
@Column()
映射数据库字段。如 Entity 属性的username => 数据库字段的username。 @Column() 常见参数:
| 参数 | 类型 | 作用 |
|---|---|---|
type |
string |
数据库字段类型 |
name |
string |
数据库字段名 |
length |
number |
字符串长度 |
nullable |
boolean |
是否允许 NULL,默认值 false |
unique |
boolean |
是否唯一 |
default |
- | 默认值 |
select |
boolean |
查询时是否默认返回,默认值 true 。密码字段常设置为 false |
enum |
`` | 枚举值 |
precision |
`` | decimal 总位数 |
scale |
`` | decimal 小数位数 |
// 对应:name VARCHAR(50)
@Column({
type: 'varchar',
length: 50,
})
name: string;
// 对应:age INT NULL。false对应 NOT NULL
@Column({
nullable: true,
})
age: number | null;
@Column({
unique: true,
})
email: string;
// TypeScript:user.username,数据库:user_name。也就是可以让"类属性名 ≠ 数据库字段名"
@Column({
name: 'user_name',
})
username: string;
@CreateDateColumn()
添加数据时,自动新增时间。格式为: yyyy-mm-dd hh:mm:ss
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
修改数据时自动更新时间
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
删除时自动记录时间。用于 软删除
@DeleteDateColumn()
deletedAt: Date;
Entity 和 DTO的区别
两者都是数据模型,但是DTO用于描述接口输入/输出数据,Entity用于描述数据库数据。
Repository
专门负责操作某一个 Entity 的数据库工具对象。
获取Repository
注册
// 假设User的 Entity 已经定义好
@Module({
imports: [
// 在当前模块里注册 User 对应的 Repository
TypeOrmModule.forFeature([User]),
],
providers: [UsersService],
})
export class UsersModule {}
注入和获取
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
// 注入 User 对应的 Repository
private readonly userRepository: Repository<User>,
) {}
// 通过this.userRepository获取,这样就可以操作User表。如this.userRepository.find();
}
常用API
| API | 作用 |
|---|---|
create() |
创建 Entity 对象,但不写数据库 |
save() |
保存写入库。用于新增或更新 |
find() |
查询多条 |
findBy() |
按简单条件查询多条 |
findOne() |
查询一条 |
findOneBy() |
按简单条件查询一条 |
update() |
直接更新 |
delete() |
直接删除 |
remove() |
删除 Entity |
count() |
数量统计 |
exists() |
判断是否存在 |
findAndCount() |
查询列表 + 总数 |
create()
创建 Entity 对象。create() 只是创建 Entity 实例,不执行 INSERT,也就是它不会操作数据库,需要通过 save() 保存才能入库。create是同步的。
参数与返回值
// 传对象
repository.create(
data: DeepPartial<Entity>
): Entity;
// 传数组
repository.create(
data: DeepPartial<Entity>[]
): Entity[];
| 参数 | 含义 |
|---|---|
data |
符合 Entity 结构的对象或“部分对象”(即不需要所有字段全部传),或者这种对象组成的数组 |
示例
const user = this.userRepository.create({
username: "Tom",
age: 20,
});
// 返回:User { username: 'Tom', age: 20 }
save()
保存 Entity
参数与返回值
// 传对象
repository.save(
entity: DeepPartial<Entity>,
options?: SaveOptions,
): Promise<Entity>;
// 传数组
repository.save(
entities: DeepPartial<Entity>[],
options?: SaveOptions,
): Promise<Entity[]>;
| 参数 | 含义 |
|---|---|
Entity |
符合 Entity 结构的对象或“部分对象”(即不需要所有字段全部传),或者这种对象组成的数组 |
options |
高级配置 |
示例
const user = this.userRepository.create({
username: "Tom",
age: 20,
});
await this.userRepository.save(user);
// 也可以用以下方式,但是推荐通过create显示创建
// await this.userRepository.save({
// username: 'Tom',
// age: 20,
// });
// 可能返回:
// {
// id: 1,
// username: 'Tom',
// age: 20,
// isActive: true
// }
当数据不存在,save是INSERT,当数据存在,save是UPDATE
find()
查询多条数据。如果一条也没找到,返回空数组。
参数和返回值
repository.find(
options?: FindManyOptions<Entity>
): Promise<Entity[]>;
| 参数 | 含义 |
|---|---|
options |
查询配置对象 |
示例
const users = await this.userRepository.find({
where: {
isActive: true,
},
order: {
id: "DESC",
},
});
findBy()
按照简单条件查询多条
repository.findBy(
where:
| FindOptionsWhere<Entity>
| FindOptionsWhere<Entity>[]
): Promise<Entity[]>;
| 参数 | 含义 |
|---|---|
where |
where 条件 |
示例
const users = await this.userRepository.findBy({
age: 20,
isActive: true,
});
// 相当于:WHERE age = 20 AND isActive = true
findOne()
查询一条数据
repository.findOne(
options: FindOneOptions<Entity>
): Promise<Entity | null>;
| 参数 | 含义 |
|---|---|
options |
查询配置对象 |
示例
const user = await this.userRepository.findOne({
where: {
id: 1,
},
});
findOneBy()
按照简单条件查询一条数据
参数和返回值
repository.findOneBy(
where:
| FindOptionsWhere<Entity>
| FindOptionsWhere<Entity>[]
): Promise<Entity | null>;
| 参数 | 含义 |
|---|---|
where |
where 条件 |
const user = await this.userRepository.findOneBy({
id: 1,
});
update()
直接更新数据
参数与返回值
repository.update(
criteria: id | ids | FindOptionsWhere<Entity>,
partialEntity: QueryDeepPartialEntity<Entity>,
options?: UpdateOptions,
): Promise<UpdateResult>;
| 参数 | 含义 |
|---|---|
criteria |
需要被修改的数据 |
partialEntity |
修改后的新值 |
options |
class UpdateResult {
raw: unknown;
affected?: number;
generatedMaps: object[];
}
| 参数 | 含义 |
|---|---|
raw |
数据库驱动返回的原始结果,未经 TypeORM 统一格式化。无需使用,直接使用 affected 进行判断即可 |
affected |
本次 UPDATE SQL 实际影响了多少行数据 |
generatedMaps |
数据库额外生成了哪些实体字段 |
示例
const result = await this.userRepository.update(1, {
age: 21,
});
// 相当于UPDATE users SET age = 21 WHERE id = 1;
// 注意返回值是result,不是User。如果想要User可以使用findOneBy查询
delete()
直接删除
参数和返回值
repository.delete(
criteria: | id | id[] | FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[]
): Promise<DeleteResult>;
| 参数 | 含义 |
|---|---|
criteria |
要被删除的数据 |
示例
await this.userRepository.delete(1); // 相当于 DELETE FROM users WHERE id = 1;
remove()
删除 Entity ,需要传入实体,所以使用 remove 前需要获取实体。
参数和返回值
// 传对象
repository.remove(
entity: Entity,
options?: RemoveOptions
): Promise<Entity>;
// 传数组
repository.remove(
entities: Entity[],
options?: RemoveOptions
): Promise<Entity[]>;
| 参数 | 含义 |
|---|---|
entity |
实体 |
options |
高级配置对象 |
示例
const user = await repository.findOneBy({ id });
await repository.remove(user);
exists()
判断数据是否存在
参数和返回值
repository.exists(
options?: FindManyOptions<Entity>
): Promise<boolean>;
| 参数 | 含义 |
|---|---|
options |
完整查询配置 |
示例
const exists = await this.userRepository.exists({
where: {
username: "Tom",
},
});
existsBy()
使用简单条件判断存在
参数与返回值
repository.existsBy(
where:
| FindOptionsWhere<Entity>
| FindOptionsWhere<Entity>[]
): Promise<boolean>;
| 参数 | 含义 |
|---|---|
where |
where 条件 |
示例
const exists = await this.userRepository.existsBy({
username: "Tom",
});
count()
统计数量
repository.count(
options?: FindManyOptions<Entity>
): Promise<number>;
参数和返回值
| 参数 | 含义 |
|---|---|
options |
完整查询配置 |
示例
const count = await this.userRepository.count({
where: {
isActive: true,
},
});
countBy()
使用简单条件统计数量
参数和返回值
repository.countBy(
where:
| FindOptionsWhere<Entity>
| FindOptionsWhere<Entity>[]
): Promise<number>;
| 参数 | 含义 |
|---|---|
where |
where 条件 |
示例
const count = await this.userRepository.countBy({
isActive: true,
});
findAndCount()
查询数据和总数量,分页很常用
参数和返回值
repository.findAndCount(
options?: FindManyOptions<Entity>
): Promise<[Entity[], number]>;
| 参数 | 含义 |
|---|---|
options |
完整查询配置 |
注意返回值是个tuple,前面为数据,后面为数量。如[ User[], number ]
示例
const [users, total] = await repository.findAndCount({
skip: (pages - 1) * pageSize,
take: pageSize,
});
findAndCountBy()
使用简单条件查询数据和总数量
参数和返回值
repository.findAndCountBy(
where:
| FindOptionsWhere<Entity>
| FindOptionsWhere<Entity>[]
): Promise<[Entity[], number]>;
| 参数 | 含义 |
|---|---|
where |
where 条件 |
示例
const [users, total] = await repository.findAndCountBy({
isActive: true,
});
findOneOrFail()、findOneByOrFail()
和 findOne() 、 findOneBy() 作用一样,区别是 findOneOrFail() 、findOneByOrFail() 没有找到数据直接抛出异常。
sum() / average() / minimum() / maximum()
对查询结果进行运算,所以要求操作列必须是数字。
参数和返回值
repository.sum(
columnName: 数字字段名,
where?: FindOptionsWhere<Entity>
): Promise<number | null>;
| 参数 | 含义 |
|---|---|
columnName |
数据库字段名 |
where |
where 条件 |
示例
// 求age列符合条件的数据的值总和
const totalAge = await repository.sum("age", {
isActive: true,
});
查询配置对象FindOneOptions
查询配置对象 FindManyOptions 则是在 FindOneOptions 的基础上额外多了分页功能的 skip 和 take 这两个属性。
interface FindOneOptions<Entity> {
// 查询哪些字段
select?: FindOptionsSelect<Entity>;
// WHERE 条件
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[];
// 加载哪些关联关系
relations?: FindOptionsRelations<Entity>;
// 关系加载方式
relationLoadStrategy?: "join" | "query";
// ORDER BY
order?: FindOptionsOrder<Entity>;
// 查询缓存
cache?: boolean | number | { id: any; milliseconds: number };
// 数据库锁
lock?: LockOptions;
// 是否包含软删除数据
withDeleted?: boolean;
// 是否只加载 Relation ID
loadRelationIds?:
| boolean
| { relations?: string[]; disableMixedMap?: boolean };
// 是否加载 eager 关系
loadEagerRelations?: boolean;
// 查询是否放入事务执行
transaction?: boolean;
// 给 SQL 添加注释
comment?: string;
}
select属性
设置要查询的字段,不传入默认查询全部字段
select?: {
id?: boolean; // true表示返回该字段,false表示不返回
age?: boolean;
isActive?: boolean;
profile?: ...;
posts?: ...;
}
where属性
设置 where 条件。 键值对形式默认为 = , 一个对象中多个属性为 AND ,对象数组为 OR
await repository.find({
// 相当于 WHERE age = 20 AND isActive = true
where: { age: 20, isActive: true },
});
await repository.find({
// 相当于 WHERE username = 'Tom' OR profile.city = 'Shenzhen'
where: [
{
username: "Tom",
},
profile: {
city: 'Shenzhen',
},
],
});
其他 where 条件
| API | 参数 | 对应的SQL |
|---|---|---|
Equal(value) |
一个值 | = |
Not(value) |
值 / Operator | NOT / != |
MoreThan(value) |
一个值 | > |
MoreThanOrEqual(value) |
一个值 | >= |
LessThan(value) |
一个值 | < |
LessThanOrEqual(value) |
一个值 | <= |
Between(a,b) |
起点、终点 | BETWEEN |
In(array) |
数组 | IN (...) |
Like(pattern) |
LIKE 字符串 | LIKE |
ILike(pattern) |
大小写不敏感 LIKE | ILIKE 等 |
IsNull() |
无 | IS NULL |
And(...) |
多个 Operator | AND |
Or(...) |
多个 Operator | OR |
Raw(...) |
SQL 表达式 | 自定义 |
示例
const users = await this.userRepository.find({
where: {
age: Between(18, 30),
username: Like("%Tom%"),
id: In([1, 2, 3, 4, 5]),
isActive: true,
},
});
// 对应SQL
// SELECT *
// FROM users
// WHERE age BETWEEN 18 AND 30
// AND username LIKE '%Tom%'
// AND id IN (1, 2, 3, 4, 5)
// AND isActive = true;
relations属性
查询关联数据,涉及到多表查询可以使用。
relationLoadStrategy属性
控制 TypeORM 用什么方式加载 relations,取值为 'join'| 'query' 。 join 类似 LEFT JOIN 。
order属性
设置排序。升序: ASC 或 asc 或 1 ,降序: DESC 或 desc 或 -1 。
多字段排序
// 对应:ORDER BY age DESC, id ASC
order: {
age: 'DESC',
id: 'ASC',
}
skip属性
表示跳过多少条。该属性只存在于 FindManyOptions 。
await repository.find({
skip: 20, // 跳过前20条,从21条开始
});
// 对应:OFFSET 20
take
最多获取多少条。该属性只存在于 FindManyOptions 。
await repository.find({
take: 10, // 最多获取十条
});
// 对应:LIMIT 10
withDeleted属性
是否包含软删除数据