Skip to content

Docker安装配置本地MYSQL服务访问

docker的安装此处不再赘述。

创建docker-compose.yml

在项目的根目录下创建Docker容器应用的配置文件:docker-compose-dev.yml,并定义服务镜像

yaml
# docker-compose.yml

# Use root/example as user/password credentials
version: '3.1'

services:
  db:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
    	# 数据库的访问密码,请注意默认的账号名称是root
      MYSQL_ROOT_PASSWORD: thisispassword  
      # 默认创建的数据库
      MYSQL_DATABASE: testdb
    ports:
      - 3307:3306

  # navicat
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

这里定义了两个镜像:

  • mysql的镜像,username默认为root,password为 example,database是:testdb
  • adminer管理数据库简单而强大的工具,方便可视化查看数据,通过localhost:8080访问页面

在根目录下的控制台启动Docker的镜像服务

shell
docker-compose -f docker-compose-dev.yaml up -d

浏览器访问:localhost:8080访问adminer页面,首先需要登录

image.png

此处需要注意的是,服务器这个输入框中是可以连接远程mysql数据库的。例如:119.96.24.5:3306这样的

输入上面配置Mysql image账号信息,可以看到testdb数据里无table

image.png

对接数据库

作为后端项目,必须得连接数据库。在这里,我们采用Mysql,毕竟相对来说Mysql应用广泛且非常稳定,有非常活跃的社区支持。

前置条件:环境配置

NestJS 本身也自带了多环境配置方法。使用 @nestjs/config 会默认从项目根目录载入并解析一个 .env 文件。

安装依赖

npm i @nestjs/config -S

安装完毕后,在 app.module.ts 中添加 ConfigModule 模块

typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { ConfigModule } from '@nestjs/config';

@Module({
    imports: [ConfigModule.forRoot(), UserModule],
    controllers: [AppController],
    providers: [AppService],
})

export class AppModule {}

使用 @nestjs/typeorm连接数据库

安装依赖

npm i @nestjs/typeorm typeorm mysql2 -S

其次我们在根目录下创建env文件用于存放数据库配置信息

// 数据库地址
DB_HOST=localhost
// 数据库端口
DB_PORT=3306
// 数据库登录名
DB_USER=root
// 数据库登录密码
DB_PASSWD=root
// 数据库名字
DB_DATABASE=blog

然后在 app.module.ts 中连接数据库

typescript
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService, ConfigModule } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import * as path from 'path';

const envPath = path.resolve('.env');

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: [envPath],
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        type: 'mysql', // 数据库类型
        entities: [],  // 数据表实体
        host: configService.get('DB_HOST', 'localhost'), // 主机,默认为localhost
        port: configService.get<number>('DB_PORT', 3306), // 端口号
        username: configService.get('DB_USER', 'root'),   // 用户名
        password: configService.get('DB_PASSWORD', 'root'), // 密码
        database: configService.get('DB_DATABASE', 'blog'), //数据库名
        timezone: '+08:00', //服务器上配置的时区
        synchronize: true, //根据实体自动创建数据库表, 生产环境建议关闭
      }),
    }),
    UserModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

数据库的增删改查

通过typeORM已经连接好了数据库,那么我们现在要创建数据表,并通过接口实现CRUD功能。

创建 user.entity.ts 实例

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

@Entity('user')
export class User {
    @PrimaryGeneratedColumn()
    id: number; // 标记为主列,值自动生成
    
    @Column({ default: null })
    name: string;
    
    @Column({type: 'timestamp', default: () => "CURRENT_TIMESTAMP"})
    create_time: Date;
    
    @Column({type: 'timestamp', default: () => "CURRENT_TIMESTAMP"})
    update_time: Date;
}

修改 user.service.ts 文件,设置 CRUD

typescript
import { HttpException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private readonly userRepository: Repository<User>,
    ) {}

    async create(createUserDto: Partial<User>): Promise<User> {
        const { name } = createUserDto;
        if (!name) {
            throw new HttpException('名字不能为空', 401);
        }
        const userInfo = await this.userRepository.findOne({ where: { name } });
        if (userInfo) {
            throw new HttpException('名称不能重复', 401);
        }
        return await this.userRepository.create(createUserDto);
    }

    async findById(id): Promise<User> {
        return await this.userRepository.findOne(id);
    }

    async updateById(id, user): Promise<User> {
        const existUser = await this.userRepository.findOne(id);
        if (!existUser) {
            throw new HttpException('用户不存在', 401);
        }
        const updateUser = this.userRepository.merge(existUser, user);
        return this.userRepository.save(updateUser);
    }

    async remove(id) {
        const existUser = await this.userRepository.findOne(id);
        if (!existUser) {
            throw new HttpException('用户不存在', 401);
        }
        return await this.userRepository.remove(existUser);
    }
}

user.module.ts 加上相关引入

typescript
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
    imports: [TypeOrmModule.forFeature([User])],
    controllers: [UserController],
    providers: [UserService],
})
export class UserModule {}

user.controller.ts 加上相关路由

typescript
import {
    Controller,
    Get,
    Post,
    Body,
    Patch,
    Param,
    Delete,
    Put,
} from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
    constructor(private readonly userService: UserService) {}

    @Post()
    async create(@Body() createUserDto) {
        return await this.userService.create(createUserDto);
    }

    @Get(':id')
    async findById(@Param('id') id: string) {
        return await this.userService.findById(id);
    }

    @Put(':id')
    async update(@Param('id') id: string, @Body() updateUserDto) {
        return await this.userService.updateById(id, updateUserDto);
    }

    @Delete(':id')
    async remove(@Param('id') id: string) {
        return await this.userService.remove(id);
    }
}

Typeorm补充参考资料1

1.定义表结构

ts
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 当记录新增时会写入时间

2.引用表

ts
// 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
ts
// 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 能看表的详细信息。

shell
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 |

3.联表查询

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

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

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

ts
// 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 修改

ts
// 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)进行关联。

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

sql
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 引入表

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 方法

ts
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

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

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

    students: number[]
}

students.controller.ts 修改

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);
    }
}

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

js
// 再新增一条数据
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"
    }]
}]