nestjs学习之数据库连接工具

下来这篇文章我们来讲解一下nestjs中使用数据库,不了解数据库操作的同学,请查看这里

Nodejs ORM框架

下载趋势比较

ORM特点

  • 方便维护:数据模型定义在同一个提防,利于重构。
  • 代码量少,对接多种库。
  • 工具多,自动化能力强(数据库删除关联数据,事务操作等)
  • 他是基于配置做sql操作的,所以我们不能直接书写sql,从而进行特别大的性能优化。

常见数据库

关系型:MySQL、Oracle、SQL Server、Access、SQLite

非关系型:MonogoDB、Redis、Hbase、Memcache。数据模型变动比较大,数据结构不确定。

关系型数据库特点

优点:易于维护、使用方便、支持复杂查询效率高

缺点:读写性能差,灵活性差

场景:各类业务系统、管理系统、安全性较高的场景

非关系型数据库特点

优点:易于扩展,大文件存储,查询速度快

缺点:复杂计算与联合查询效率低,

场景:多格式&海量数据、分布式消息系统、统计排行

分多张表: 方便索引,结构清晰,防止数据冗余

nestjs结合typeORM

nestjs默认集成了typeORM模块,我们只需要安装对应的库然后进行配置即可。@nestjs/typeorm, typeorm, mysql2

js 复制代码
npm install --save @nestjs/typeorm typeorm mysql2

Nest 使用TypeORM是因为它是 TypeScript 中最成熟的对象关系映射器( ORM )。因为它是用 TypeScript 编写的,所以可以很好地与 Nest 框架集成。

基础配置

js 复制代码
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  // 导入模块
  imports: [
    // 配置数据库连接信息
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'xxx',
      database: 'xx',
      entities: [],
      // 同步本地的schema与数据库 -> 初始化的时候去使用
      synchronize: true,
      // 日志等级
      logging: ['error'],
    }),
    UserModule,
  ],
  // 注册控制器
  controllers: [AppController, UserController],
  // 依赖注入,在控制器中自动实例化该服务
  providers: [AppService, UserService],
})

为了更易于扩展,我们可以提供环境变量给数据库连接配置提供属性

js 复制代码
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

const envFilePath = `.env.${process.env.NODE_ENV || 'development'}`;
@Module({
  // 导入模块
  imports: [
    // 工厂模式返回,使用环境变量中的配置
    TypeOrmModule.forRootAsync({
      // 为了可以使用ConfigService实例对象获取环境变量
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory(configService: ConfigService) {
        return {
          type: 'mysql',
          host: configService.get('DB_HOST'),
          port: configService.get('DB_PORT'),
          username: configService.get('DB_USER_NAME'),
          password: configService.get('DB_PASSWORD'),
          database: configService.get('DB_DATE_BASE'),
          entities: [],
          // 同步本地的schema与数据库 -> 初始化的时候去使用
          synchronize: true,
          // 日志等级
          logging: ['error'],
        };
      },
    }),
    UserModule,
  ],
  // 注册控制器
  controllers: [AppController, UserController],
  // 依赖注入,在控制器中自动实例化该服务
  providers: [AppService, UserService],
})

具体引入步骤请看这里

数据库设计

  • 原子性,要求属性设计具有原子性。不能存在嵌套属性,需要借助外键关联多个表。
  • 记录唯一性,表示一个表中所有字段没有依赖关系。(即非主键字段必须依赖主键字段,即主键由单一属性组成)否则可能出现数据冗余,删除异常,插入异常,更新异常等等。
js 复制代码
表:职工号 姓名 职称 项目号 项目名称

职工号 -> 姓名,职称
项目号 -> 项目名称

正确设计

职工信息表:职工号,姓名,职称
项目信息表:项目号,项目名称
  • 第三范式。当属于第二范式后,并且在两个或多个非主键属性之间不存在函数依赖(传递依赖)。它是对字段的冗余性做限制,要求任何字段不能有其他字段派生出来。
js 复制代码
表:学号 姓名 年龄 性别 所在院校 院校地址 院校电话

上述属于第二范式,因为主键有单一属性组成。但是承载依赖传递 (学号 - 学生 - 所在学院 - 学院电话)

正确设计

学生表:学号 姓名 年龄 所在学院

学院表:学院 学院名称 电话

配置好ORM实体类并注册后,自动在连接的数据库中创建对应的表以及表结构。注意:实体类中只设置应有的属性字段,外键我们是通过注解去配置的,而不需要设置在实体类中。

js 复制代码
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

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

  @Column()
  username: string;

  @Column()
  password: string;
}
js 复制代码
TypeOrmModule.forRootAsync({
  // 为了可以使用ConfigService实例对象获取环境变量
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory(configService: ConfigService) {
    return {
      type: 'mysql',
      host: configService.get('DB_HOST'),
      port: configService.get('DB_PORT'),
      username: configService.get('DB_USER_NAME'),
      password: configService.get('DB_PASSWORD'),
      database: configService.get('DB_DATE_BASE'),
      // 注册实体类
      entities: [User],
      // 同步本地的schema与数据库 -> 初始化的时候去使用
      synchronize: true,
      // 日志等级
      logging: ['error'],
    };
  },
}),

这里需要了解一些注解。

普通注解

  • Column 表示列属性
  • PrimaryGeneratedColumn 表示主键自增

关系注解

对于多对一和一对一,我们只需要在对应的OneToOne,OneToMany中指定第二个参数与主表的映射属性即可。在主表中并不需要指定。设置JoinColumn注解即可。

一对一

  • OneToOne 一对一关系。我们在映射的时候并不关系主键外键,他们只是一个桥接的作用,我们主要是获取两个表中的其他列属性。所以需要映射一个实体类。
  • JoinColumn 连接外部表在当前表中的烈属性。即外键。
js 复制代码
// 设置外键映射的实体类
@OneToOne(() => User)
// 默认键名叫做 关联表名+主键名称,小驼峰形式。但是我们也可以手动设置
// 例如外表主键是index,那么这个外键命名就是userIndex
@JoinColumn({ name: 'userId' })
user: User;
js 复制代码
// 这里需要指定关联属性
@OneToOne(() => Profile, (profile) => profile.user)
profile: Profile;

一对多,多对一

ManyToOne 多对一。OneToMany 一对多。

我们关联多对一关系时,需要在多的那方建立外键,作为主表。@ManyToOne可以单独使用,但@OneToMany必须搭配@ManyToOne使用。

其实这里的JoinColumn注解是不需要的,因为OneToMany, ManyToOne 的第二个参数已经可以建立联系了。 或者删除(user) => user.logs(user) => user.logs这种方式是给高级语言看的,而JoinColumn是数据库进行查询找到属性的。

js 复制代码
// 第一个参数 指定返回的数据类型是啥样的
// 第二个参数 表示查询时的条件
@ManyToOne(() => User, (user) => user.logs)
// 建立多表连接(外键和主键链接)
@JoinColumn()
user: User;
js 复制代码
// 一对多
// 一个用户对应多个操作日志。
// 多对一时,主表就是多的那方,在主表中建立一个外键绑定user的主键。 
// 第二个参数就是和logs表的连接条件 user.id === logs.userId
@OneToMany(() => Logs, (logs) => logs.user)
logs: Logs[];

多对多

ManyToMany 建立多对多关系。我们知道多对多关系,主要通过第三个表进行建立。所以需要用到JoinTable注解。

js 复制代码
// 多对多
@ManyToMany(() => Roles, (roles) => roles.users)
// 建立额外关联表
@JoinTable()
roles: Roles[];
js 复制代码
// 多对多
@ManyToMany(() => User, (user) => user.roles)
users: User[];

如何将已存在的数据库映射生成ORM实体类文件

通过typeorm-model-generator去通过数据库表结构生成对应的实体类。 然后配置运行命令

js 复制代码
typeorm-model-generator -h 域名 -p 端口号 -d 数据库 -u 用户名 -x 密码 -e 数据库类型 -o .

DI容器工作原理

所以我们使用typeOrm提供的数据库操作方法,只需要在对应各模块中导入即可被nestjs收集,并自动实例化。

js 复制代码
// user.module.ts
@Module({
  // 在module中导入的依赖就会被自动实例化。
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

数据库crud测试

一个crud。下面这个是单表的数据库操作。

js 复制代码
// user.service.ts
import { User } from 'src/user/user.entity';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

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

  /**
   * 查找全部
   */
  async findAll() {
    return this.userRepository.find();
  }
  /**
   * 查找指定的
   */
  async findById(id: number) {
    return this.userRepository.find({ where: { id } });
  }
  /**
   * 添加用户
   */
  async createUser(user: User) {
    // 创建一个数据库实体类
    const dbUser = await this.userRepository.create(user);
    return this.userRepository.save(dbUser);
  }
  /**
   * 更新用户
   */
  async updateUser(id: number, user: Partial<User>) {
    return this.userRepository.update(id, user);
  }
  /**
   * 删除用户
   */
  async removeUser(id: number) {
    return this.userRepository.delete(id);
  }
}
js 复制代码
// user.controller.ts
import { User } from 'src/user/user.entity';
import { ConfigService } from '@nestjs/config';
import {
  Controller,
  Get,
  Param,
  Query,
  Post,
  Body,
  Put,
  Delete,
} from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(
    private userService: UserService,
    private config: ConfigService,
  ) {}

  @Get('getAllUser')
  async getAllUser() {
    return this.userService.findAll();
  }

  @Get('getUserById/:id')
  async getUserById(@Query() { id }) {
    return this.userService.findById(id);
  }

  @Post('createUser')
  async createUser(@Body() user: User) {
    return this.userService.createUser(user);
  }

  @Put('updateUser')
  async updateUser(@Body() user: User) {
    const id = user.id;
    delete user.id;
    return this.userService.updateUser(id, user);
  }

  @Delete('deleteUserById')
  async deleteUserById(@Param() { id }) {
    return this.userService.removeUser(id);
  }
}

具体可以看这里

多表联查

我们在进行实体类之间关系设计时,记住这句话就可以。对于多对一和一对一,我们只需要在对应的OneToOne,OneToMany中指定第二个参数与主表的映射属性即可。在主表中并不需要指定,设置JoinColumn注解即可。

我们进行条件查询时,where中指定的属性就是当前实体类中定义的属性。

js 复制代码
// 关联查询 (一对一)
  async findProfile(id: number) {
    return this.userRepository.findOne({
      where: { id },
      relations: {
        profile: true,
      },
    });
  }

  // 多对多(以user为主记录)
  async findLogs(id: number) {
    return this.userRepository.findOne({
      where: { id },
      relations: {
        logs: true,
      },
    });
  }
  
  // 多对多(以logs为主记录)
  // async findLogs(id: number) {
  //   const user = await this.findById(id);
  //   return this.logsRepository.find({
  //     // 这里的条件就是logs实体类的属性
  //     where: { user },
  //     relations: {
  //       user: true,
  //     },
  //   });
  // }

以上即是简单的介绍,具体请看typerOrm官网

往期年度总结

往期文章

专栏文章

相关推荐
不懂装懂的不懂3 分钟前
【vue】vue运行报错“Error:listen EACCES:permission denied”
前端
lfl183261621603 分钟前
thingsboard 自定义html
java·前端·html
传说中胖子5 分钟前
在线excel编辑(luckysheet)
javascript
中國移动丶移不动9 分钟前
Java 并发编程:原子类(Atomic Classes)核心技术的深度解析
java·后端
ekskef_sef12 分钟前
前端Vue框架基础介绍
前端·javascript·vue.js
椒盐大肥猫18 分钟前
vue3 Proxy替换vue2 defineProperty的原因
前端·javascript·vue.js
孙 悟 空21 分钟前
uni-app:监听页面返回,禁用返回操作
前端·javascript·uni-app
m0_5485030328 分钟前
【Java Web】Tomcat 快速入门
java·前端·tomcat
我码玄黄31 分钟前
正则表达式优化之算法和效率优化
前端·javascript·算法·正则表达式
m0_7482515233 分钟前
vue2前端导出pdf文件
前端·pdf·状态模式