返回文章列表 →

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属性

是否包含软删除数据