nest学习笔记(6) :联通数据库的CRUD操作以及高级查询

重新回忆依赖注入

  • 我们都知道nestjs最核心的概念就是依赖注入,所有的开发思想都是基于这个概念来实现出来的,因此nestjs官方将所有数据库的常见操作都高度封装在TypeORM这个核心包里面,这就意味着在项目中做CRUD操作时,我们无需再写SQL语句

image

通过上图我们可以看出,Nestjs中的DI容器会自动的将项目中携带@Injectable装饰符的类进行注册,最后会进一步看该类中的构造函数(Constructor),了解对应的类与类之间的依赖关系之后进行对应类实例的创建,在项目中用的时候引入即可!

CRUD操作

关联查询: 一对一,一对多

一对一

  • 所谓一对一关联查询,就是在数据库中,某一张表与另一张表建立外键约束,我们在查询其中一张表数据的时候,同时返回外键约束的那张表的对应数据,这里我们展示User表以及Profile表建立的外键约束关系(UserId)

1. 首先确定两张表的实体中都设置了一对一关系(OneToOne)

// user.entities.ts(user表实体)
// 编写实体,用于一一对应数据库中的数据映射关系
import {
/*
其他定义方法...
*/
OneToOne,
} from 'typeorm';
import { Profile } from './profile.entities';

// 定义一个实体类
/*
当然我们也可以自定义对应数据映射到数据库中的存储属性
我们可以设置存储的类型,长度等..
*/
@Entity()
export class User {
/*
省略其他字段定义 ...
*/

// 定义一对一关系(第二个参数一定要设置,用于及案例映射关系)
@OneToOne(() => Profile, (profile) => profile.user)
/*
第二个参数解析,虽然在数据库中的表设置的是user_id为外键名(字段),但是在实体中(ts)
数据库中的字段仍为 user
*/
profile: Profile;
}

/* --------------------------------------------------------------------------- */

// profile.entities.ts(profile表实体)
// 编写实体,用于一一对应数据库中的数据映射关系
import {
/*
其他定义方法...
*/
OneToOne,
} from 'typeorm';
import { User } from './user.entities';

@Entity()
export class Profile {
/*
省略其他字段定义 ...
*/

// 建立与user实体的一对一映射关系
@OneToOne(() => User)
// 不设置的话,typeOrm会自动将下面的属性名拼接上主表的主键属性名 - userId(驼峰)
@JoinColumn({ name: 'user_id' }) //给当前表加入一个字段(一列)
user: User;
/*
在 Profile 实体类中声明一个名为 user 的字段,该字段的类型为 User 实体类。
通过使用 @OneToOne(() => User) 注解,指定了 user 字段与 User 实体类的 id 字段进行关联(默认主键)
即将 User 实体类的 id 字段与 Profile 实体类的 user_id 字段进行了一对一的映射。
*/
}

2. 编写查询的业务逻辑(开启relations属性)

// user.service.ts (业务逻辑处理)
import { Injectable } from '@nestjs/common';

/*
操作数据库引入模块
1. Repository 在typeorm中引入可操作数据库实体的方法
2. InjectRepository 注入仓库,将实体中的数据注入到数据库仓库中去
3. 我们新建的实体,一一对应数据库中的数据
*/
import { Like, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entities';

@Injectable()
export class BoysService {
// 注入依赖以及绑定实体数据库
constructor(
// 基本是固定写法
@InjectRepository(User) private readonly users: Repository<User>, // 一对一关系(User_Profile)
) {}

// 关联查询: 一对一
/*
重点 :
这里一定要理解清楚:这种写法是在查询用户数据的情况下
将该用户对应的profile数据查询出来,因此条件需要使用
user实体中定义的,两者的关联字段是user_id(关联的是用户的id)
因此这里的一对一查询是查询用户数据的同时返回该用户对应的 profile
数据
*/
// 1. 查询用户时对应查询出该用户对应的profile
findUser_Profile(id: number) {
return this.users.findOne({
where: {
id, // 查询条件
},
// 设置关联查询对应的表
relations: {
profile: true, // 关联 profile 表
},
});
}
}

3. 编写一对一关联查询接口

// user.controller.ts(编写接口)
import {
Controller,
Get,
/*
其他定义方法...
*/
Inject,
} from '@nestjs/common';
import { BoysService } from './boys.service';

@Controller('boys')
export class BoysController {
// 创建一个构造函数 (注入依赖)
constructor(
// 注入service依赖
@Inject('boys') private BoysService: BoysService,
) {
/*
BoysService: BoysService 等价于 this.BoysService = new BoysService()
*/
}

// 一对一关系的查询
@Get('/user_profile')
getUser_Profile(): any {
// 返回建立user表与profile表的userId的外键约束数据
return this.BoysService.findUser_Profile(1);// 将用户id传过去查询
}
}

结果展示:

image

一对多/多对一

  • 一对多以及多对一与上面的想法是基本一致,其实关键都是看通过谁去查询谁,这个是重点,是通过一个用户id去查询该用户的同时顺带查询该用户的全部logs数据还是再查询对应logs数据的同时顺带查询该条logs数据是属于哪一个用户的

1. 首先确定两张表都设置了一对多/多对一关系

// logs.entities.ts(日志数据表实体)
// 编写实体,用于一一对应数据库中的数据映射关系
import {
/*
其他定义方法...
*/
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from '../user.entities';

@Entity()
export class Logs {
/*
省略其他字段定义 ...
*/

// 定义多对一关系(多个日志对应一个用户)
@ManyToOne(() => User, (user) => user.logs)
@JoinColumn({ name: 'user_id' }) // 这段代码可有可无(官方推荐使用)
user: User; // 返回日志对应的user是以 User实体类 为类型的数据
}

/* ----------------------------------------------------------------- */

// user.entities.ts
// 编写实体,用于一一对应数据库中的数据映射关系
import {
/*
其他定义方法...
*/
OneToMany,
} from 'typeorm';
import { Logs } from './logs/logs.entities';

// 定义一个实体类
/*
当然我们也可以自定义对应数据映射到数据库中的存储属性
我们可以设置存储的类型,长度等..
*/
@Entity()
export class User {
/*
省略其他字段定义 ...
*/

// 定义一对多关系(一个用户对应着多个日志操作)
@OneToMany(() => Logs, (logs) => logs.user) // 可以认为第二个箭头函数是一个条件
/*
第二个参数也是一个回调函数,这个回调函数的意义是告诉sql语句,当我们
查询某一个用户信息时,请同时查询logs表中的user字段,因为这个字段是
建立 OneToMany 关系的核心,通过两者的及案例关系查询到的 logs(日志数据)
返回到 下面的 logs 字段中,当查询到这个用户时同步返回 logs数组数据
*/
logs: Logs[]; // 返回的日志数据是以 Logs实体类为子项的数组
}

2. 编写查询的业务逻辑(开启relations属性)

// user.service.ts (业务逻辑处理)
import { Injectable } from '@nestjs/common';

/*
操作数据库引入模块
1. Repository 在typeorm中引入可操作数据库实体的方法
2. InjectRepository 注入仓库,将实体中的数据注入到数据库仓库中去
3. 我们新建的实体,一一对应数据库中的数据
*/
import { Like, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entities';
import { Logs } from './entities/logs/logs.entities';

@Injectable()
export class BoysService {
// 注入依赖以及绑定实体数据库
constructor(
// 基本是固定写法
@InjectRepository(Logs) private readonly logs: Repository<Logs>, // 一对多关系(user_logs)
) {}


// 1. 关联查询: 一对多
/*
重点 :
这里一定要理解清楚:这种写法是在查询 User 数据的情况下
将这个用户对应的Logs(日志)数据查询出来,因此条件需要使用
User实体中定义的,两者的关联字段是user_id(关联的是用户的id)
因此这里的一对多查询是查询用户数据的同时返回该用户对应的 Logs
数据
*/
// 查询用户时顺带查询该用户对应的logs数据
async findUser_Logs(id: number) {
return this.users.find({
where: {
id, // 传入对应的用户数据类
},
// 一对多/多对一关系关联的数据表查询出多条数据对应的唯一用户
relations: {
logs: true,
},
});
}

// 2. 关联查询: 多对一
/*
重点 :
这里一定要理解清楚:这种写法是在查询Logs(日志)数据的情况下
将这些日志对应的user(用户)数据查询出来,因此条件需要使用
Logs实体中定义的,两者的关联字段是user_id(关联的是用户的id)
因此这里的多对一查询是查询日志数据的同时返回这些日志对应的 User
数据
*/
// 查询logs数据时顺带查询该logs数据下对应的用户数据
async findLogs_User(id: number) {
const user = await this.findOne(id); // 获取id对应的用户数据类
return this.logs.find({
where: {
user, // 传入对应的用户数据类
},
// 一对多/多对一关系关联的数据表查询出多条数据对应的唯一用户
relations: {
user: true,
},
});
}

// 查询对应id中的user表中的数据(用于传入给一对多查询)
findOne(id: number) {
return this.users.findOne({
where: {
id,
},
});
}
}

3. 编写接口

// user.controller.ts(接口编写)
import {
Controller,
Get,
/*
其他定义方法...
*/
Inject,
} from '@nestjs/common';
import { BoysService } from './boys.service';

/*
我们可以认为 Controller 是用户配置路由的地方
根据以下代码我们如果想要获取数据就需要输入路径为: /boys/getBoys
当然我们要想在全局中设置初识路径,可以在main.ts中设置 setGlobalPrefix('全局路径')
*/
@Controller('boys')
export class BoysController {
// 创建一个构造函数 (注入依赖)
constructor(
// 注入service依赖
@Inject('boys') private BoysService: BoysService,
) {
/*
BoysService: BoysService 等价于 this.BoysService = new BoysService()
*/
}
// 一对多关系查询
@Get('/user_logs')
getUser_Logs(): any {
// 返回建立user表与profile表的userId的外键约束数据
return this.BoysService.findUser_Logs(1); // 传入user的id字段
}

// 多对一关系查询
@Get('/logs_user')
getLogs_User(): any {
// 返回建立user表与profile表的userId的外键约束数据
return this.BoysService.findLogs_User(1); // 传入user的id字段(后面再做logs的字段关联转换)
}
}

4. 结果展示:

一对多查询(查询用数据数据顺带查询该用户的logs数据)

image

多对一查询(查询logs数据时顺带查询该logs数据对应的用户是谁)

image

QueryBuilder的使用

  • 官方文档

  • QueryBuilderTypeORM 最强大的功能之一 ,它允许你使用优雅便捷的语法构建 SQL 查询,执行并获得自动转换的实体。(也就说我们可以使用QueryBuilder实现再业务场景中多个表不同字段数据的聚合,以API的形式去组装sql语句,解决联合查询,分页查询,多条件查询,聚合查询等…)

网上示例:

async getUsersByPlatformName(platformName: string){
return await this.userRepo
.createQueryBuilder('u') // 指定User別名为u
// 指定join user的roles关联属性,并指定別名为r,并设定搜寻条件
.leftJoinAndSelect('u.roles', 'r')
// 指定join user的dep關聯屬性,并指定別名為d,並設定搜尋條件
.leftJoinAndSelect('u.plat', 'p')
// 設定條件
.where('p.isActive = :isActive', {isActive: true})
.andWhere('p.platformName like :name', { name: `%${platformName.toLowerCase()}%`})
// 以age降幂排序
.orderBy('age', 'DESC')
// 回传多笔资料
.getMany();
// 回传上面API所组出來的Raw SQL, debug用
// .getSql()
}

自己的示例:

  • 需求:实现查询logs表中对应的响应状态码来实现返回对应的数据条数
import { Injectable } from '@nestjs/common';

/*
操作数据库引入模块
1. Repository 在typeorm中引入可操作数据库实体的方法
2. InjectRepository 注入仓库,将实体中的数据注入到数据库仓库中去
3. 我们新建的实体,一一对应数据库中的数据
*/
import { Like, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Logs } from './entities/logs/logs.entities';

@Injectable()
export class BoysService {
// 注入依赖以及绑定实体数据库
constructor(
// 基本是固定写法
@InjectRepository(Logs) private readonly logs: Repository<Logs>, // 一对多关系()
) {}

// QueryBuilder写法(高级查询)
return (
this.logs
.createQueryBuilder('logs') // 指定User別名为u
// 查询logs表的result字段数据并将返回的数据重命名为result
.select('logs.result', 'result')
// 新增多一列返回数据,count
.addSelect('COUNT("logs.result")', 'count')
.leftJoinAndSelect('logs.user', 'user') // 连表查询
.where('user.id = :id', { id }) // 查询条件
.groupBy('logs.result') // 分组排序
.orderBy('count', 'DESC')
.addOrderBy('result', 'DESC')
// .offset(2) // 向后偏移两位
.limit(3) // 限制获取3条数据
// .orderBy('result', 'DESC') // 降序排列
.getRawMany() // 获取原始的数据
);
}
}
  • 编写接口
// user.controller.ts(编写接口)
import {
Controller,
Get,
/*
其他定义方法...
*/
Inject,
} from '@nestjs/common';
import { BoysService } from './boys.service';

@Controller('boys')
export class BoysController {
// 创建一个构造函数 (注入依赖)
constructor(
// 注入service依赖
@Inject('boys') private BoysService: BoysService,
) {
/*
BoysService: BoysService 等价于 this.BoysService = new BoysService()
*/
}

// QueryBulider 编写接口返回数据
@Get('/logsByGroup')
async getLogsByGroup(): Promise<any> {
const res = await this.BoysService.findLogsByGroup(1);
// 可以自定义设置返回的数据结构
// return res.map((o) => ({
// result: o.result,
// count: o.count,
// }));
return res;
}
}

结果展示:

image