NestJs 入门教程之三:数据库

·  阅读 401
NestJs 入门教程之三:数据库

这个系列的上一篇文章,实现了 Post 接口和对接口的基础操作。

而真正的服务往往包括数据存储。

本篇将介绍如何建立 NestJs 的数据库连接、并使用数据库联表查询。这样就就是完整的后台服务了。

完整示例可以在 github 找到。

本篇使用 mysql 作为数据库连接。使用 NestJs 内置的数据库连接 typeorm,可在 这里 查阅 typeorm 详细文档

一、开发准备

  1. 下载并安装 Mysql
  2. 创建 school 库
create database school;
复制代码
  1. 安装 @nestjs/typeorm typeorm mysql2
npm install --save @nestjs/typeorm typeorm mysql2
复制代码

二、数据库连接

// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { StudentsController } from './students/students.controller';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    StudentsModule,
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: '127.0.0.1',
      port: 3306,
      username: 'root',
      password: '1qaz2wsx',
      database: 'school',
      autoLoadEntities: true,
      synchronize: true, // 数据库自动同步 entity 文件修改
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

复制代码

初始化数据库连接。

autoLoadEntities 自动化 load entity 文件, 所有在 Module 中引用的 Entity 文件会被自动加载。自动加载设置为 true 即可。

synchronize 自动化同步表,本地可自动打开,线上数据库不建议打开。

三、定义表结构

import {
    Entity,
    Column,
    PrimaryGeneratedColumn,
    UpdateDateColumn,
    CreateDateColumn,
} from 'typeorm';

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

  @Column({ type: 'varchar' })
  name: string;

  @UpdateDateColumn()
  updateDate: Date;

  @CreateDateColumn()
  createDate: Date;
}
复制代码
  • @Entity 注解代表是数据库入口文件;
  • @Column 是基础列文件,使用 type 字段定义在数据库实际存储
  • @PrimaryGeneratedColumn 代表单调递增的主键
  • @UpdateDateColumn 当记录修改时会修改时间
  • @CreateDateColumn 当记录新增时会写入时间

四、引用表

// students.module.ts
import { Module } from '@nestjs/common';
import { Student } from './entities/students.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StudentsController } from './students.controller';
import { StudentsService } from './students.service';

@Module({
    imports: [TypeOrmModule.forFeature([Student])],
    providers: [StudentsService, Student],
    controllers: [StudentsController],
})
export class StudentsModule {}
复制代码
  • imports 引用 typeorm 模块, entity 才可以在 service 中使用
  • providers service 的 constructor 需要引用哪些模块
  • controllers 模块的 controller
// students.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Student } from './entities/students.entity';

@Injectable()
export class StudentsService {
    constructor(
        @InjectRepository(Student)
        private readonly studentRepository: Repository<Student>,
    ) {}
}
复制代码

这样会在 db 中建立 students 新表。

使用 show create table 能看表的详细信息。

use school;

show tables;

// => | student | CREATE TABLE `student` (
//  `id` int NOT NULL AUTO_INCREMENT,
//  `updateDate` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
//  `createDate` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
//  `name` varchar(255) NOT NULL,
//  PRIMARY KEY (`id`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
复制代码

五、与数据库交互

到这一步,终于可以和数据库进行交互了。基本上和数据库交互的部分都会放在 service 层,因此 新增查询 都放在 service 层。

其中包括了

  • getStudentName 的改造
  • setStudent 函数的新增
// students.service.ts
// import ...

@Injectable()
export class StudentsService {
    // logger, constructor ImStudent getStudentName ...

    async getStudentName(id: number) {
        this.logger.log(`get student id is ${id}`);
        const results = await this.studentRepository.find({ id });

        return results ?? 'not found';
    }

    async setStudent(name: string) {
        const results = this.studentRepository.create({ name });
        return results;
    }
}

复制代码

通过使用 findcreate 对学生查询和创建。结果也是异步的。

下面对 controller 进行改造,使得函数调用串起来。

// students.controller.ts
// import ...
@Controller('students')
export class StudentsController {
    // constructor whoAreYou whoAreYouPost whoIsReq ..

    @Get('get-name-by-id')
    getNameById(@Query('id', ParseIntPipe) id: number) {
        return this.studentsService.getStudentName(id);
    }

    @Post('set-student-name')
    setStudentName(@User() user: string) {
        return this.studentsService.setStudent(user);
    }
}
复制代码

通过对 service 的调用, 再经 controller 调用产生如下结果

// ✅ 命令行访问
curl -X POST http://127.0.0.1:3000/students/set-student-name -H 'Content-Type: application/json' -d '{"user": "gdccwxx"}'
// => {"name":"gdccwxx","id":1,"updateDate":"2021-09-12T15:57:14.599Z","createDate":"2021-09-12T15:57:14.599Z"}%

// ✅ 浏览器访问
http://localhost:3000/students/get-name-by-id?id=1

// => [{
//  id: 1,
//  name: "gdccwxx",
//  updateDate: "2021-09-12T15:57:14.599Z",
//  createDate: "2021-09-12T15:57:14.599Z"
// }]
复制代码

通过对 service 的 save、find 调用,就能将数据完整存入数据库了。

六、联表查询

我们准备新建课程表class,每个班级可以有多个学生,一个学生隶属一个班级。

这样学生班级就构成了 n:1 的关系。

为了方便展示,在学生模块下直接新增 class.entity.ts 文件。并通过 @OneToMany 关联 students

// classes.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, UpdateDateColumn, CreateDateColumn, OneToMany } from 'typeorm';
import { Student } from './students.entity';

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

  @Column({ type: 'varchar' })
  className: string;

  @OneToMany(() => Student, student => student.class)
  students: Student[]

  @UpdateDateColumn()
  updateDate: Date;

  @CreateDateColumn()
  createDate: Date;
}
复制代码

同时修改 students.entity.ts, 通过 @ManyToOne 引入 Classes 修改

// students.entity.ts
import {
    ManyToOne,
    // Entity...
} from 'typeorm';
import { Classes } from './classes.entity';

@Entity()
export class Student {
  // id name updateDate, createDate...
  @ManyToOne(() => Classes, classes => classes.students)
  class: Classes;
}
复制代码

注意:classes 表引用 students 是通过新建字段(students\class)进行关联。

引用会最终在数据库变成外键连接。

show create table student;
// =>  CREATE TABLE `student` (
//   `id` int NOT NULL AUTO_INCREMENT,
//   `name` varchar(255) NOT NULL,
//   `updateDate` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
//   `createDate` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
//   `classId` int DEFAULT NULL,    // 👈 注意这里
//   PRIMARY KEY (`id`),
//   KEY `FK_bd5c8f2ef67394162384a484ba1` (`classId`), // 👈 注意这里
//   CONSTRAINT `FK_bd5c8f2ef67394162384a484ba1` FOREIGN KEY (`classId`) REFERENCES `classes` (`id`) // 👈 注意这里
// ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci


// 而 classes 表并无链接
show create table classes;
// CREATE TABLE `classes` (
//   `id` int NOT NULL AUTO_INCREMENT,
//   `className` varchar(255) NOT NULL,
//   `updateDate` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
//   `createDate` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
//   PRIMARY KEY (`id`)
// ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
复制代码

再引入表,详细操作可看第四步。

students.module.ts 引入表

// students.module.ts
import { Classes } from './entities/classes.entity';
// ...

@Module({
    imports: [TypeOrmModule.forFeature([Student, Classes])],
    providers: [StudentsService, Student, Classes],
    // ..
})
export class StudentsModule {}

复制代码

students.service.ts 引入表, 并实现 setClass, getClass 方法

import { Classes } from './entities/classes.entity';

@Injectable()
export class StudentsService {
    constructor(
        @InjectRepository(Student)
        private readonly studentRepository: Repository<Student>,
        @InjectRepository(Classes)
        private readonly classRepository: Repository<Classes>,
    ) {}
    // ...
     async setClass(name: string, studentIds: number[]) {
        const students = await this.studentRepository.find({ where: studentIds });
        const result = await this.classRepository.save({
            className: name,
            students: students, // 此处直接保存students 的实例,即直接从数据库取出来的数据
        })
        return result;
    }
    async findClass(id: number) {
        const result = await this.classRepository.find({
            where: { id },
            relations: ['students']
        });
        return result;
    }
}
复制代码

新增 ClassesDto

// classes.dto.ts
import { IsNotEmpty, IsString } from 'class-validator';

export class ClassesDto {
    @IsNotEmpty()
    @IsString()
    className: string;

    students: number[]
}
复制代码

students.controller.ts 修改

// students.controller.ts
// import ...
export class StudentsController {
    // constructor ...

    @Get('get-class')
    getClass(@Query('id', ParseIntPipe) id: number) {
        return this.studentsService.findClass(id);
    }

    @Post('set-class')
    setClass(@Body() classes: ClassesDto) {
        return this.studentsService.setClass(classes.className, classes.students);
    }
}

复制代码

调用接口,先插入数据再查询数据。

// 再新增一条数据
curl -X POST http://127.0.0.1:3000/students/set-student-name -H 'Content-Type: application/json' -d '{"user": "gdccwxx1"}'

// 插入 classes 数据
curl -X POST http://127.0.0.1:3000/students/set-class -H 'Content-Type: application/json' -d '{"className": "blog", "students": [1,2]}'

// ✅ 通过浏览器,查询长啥样
http://localhost:3000/students/get-class?id=1
// => [{
//  id: 1,
    className: "blog",
    updateDate: "2021-09-15T01:05:38.055Z",
    createDate: "2021-09-15T01:05:38.055Z",
    students: [{
        id: 1,
        name: "gdccwxx",
        updateDate: "2021-09-15T01:05:38.000Z",
        createDate: "2021-09-15T01:05:23.988Z"
    },{
        id: 2,
        name: "gdccwxx1",
        updateDate: "2021-09-15T01:05:38.000Z",
        createDate: "2021-09-15T01:05:28.084Z"
    }]
}]
复制代码

七、简单回顾

再回顾下本章:

  • 使用 typeormmysql 建立连接
  • 使用 entity 文件创建数据库表
  • service 使用对数据库的简单调用,包括写入读取
  • 使用关系查询,将 studentclasses 连接写入和查询

至此,我们使用 typeormmysql 连接数据库就完成了。

完整示例可以在 github 找到。

下章我们将主要讲 NestJs 的高级用法,包括 管道守卫拦截器。期待你的阅读。

如果喜欢文章,不妨点个赞。 可以在我的个人博客中找到更精彩的内容~

分类:
前端
标签: