Docker安装配置本地MYSQL服务访问
docker的安装此处不再赘述。
创建docker-compose.yml
在项目的根目录下创建Docker
容器应用的配置文件:docker-compose-dev.yml
,并定义服务镜像
# 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的镜像服务
docker-compose -f docker-compose-dev.yaml up -d
浏览器访问:localhost:8080
访问adminer
页面,首先需要登录
此处需要注意的是,服务器这个输入框中是可以连接远程mysql数据库的。例如:119.96.24.5:3306这样的
输入上面配置Mysql image
账号信息,可以看到testdb
数据里无table
对接数据库
作为后端项目,必须得连接数据库。在这里,我们采用Mysql,毕竟相对来说Mysql应用广泛且非常稳定,有非常活跃的社区支持。
前置条件:环境配置
NestJS
本身也自带了多环境配置方法。使用 @nestjs/config
会默认从项目根目录载入并解析一个 .env
文件。
安装依赖
npm i @nestjs/config -S
安装完毕后,在 app.module.ts
中添加 ConfigModule
模块
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
中连接数据库
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
实例
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
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
加上相关引入
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
加上相关路由
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.定义表结构
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.引用表
// 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 |
3.联表查询
我们准备新建课程表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"
}]
}]