最近在探究Nest,发现这东西还不错,分享给大家,上一篇文章 Nest探究(一):Nest 已经大致说明了一下Nest的结构以及Nest简单的使用,而这篇文章将会介绍Nest数据库的相关操作,如有错误,还请各位大佬指出!

连接数据库

既然是后端的项目,连接数据库是必不可少的,这里我选择更为熟悉的MySQL数据库。

而Nest当中,有很多连接数据库的 ORM (对象关系映射器),如 TypeORM Sequelize Prisma ,在此我选择 TypeORM Sequelize 实际上与 koa 一致,是使用 sqly 语句,来对数据库进行操作,而 TypeORM 它本身是由 TypeScript 写的,对 Nest 的支持相对较好,而且 Nest 也提供了 @nestjs/typeorm 包,对于一些常用的增删改查sql语句也是进行了函数封装,开箱即用,比较方便; Prisma 呼声很高,有兴趣的同学可以自行查看,在这里我使用 TypeORM

在此之前,我先解释一下,什么是 ORM ? 举个例子,我们有如下一张表,

 +----+--------+------------+ 
 | id | title | text |
 +----+-------------+--------------+
 | 1 |  标题   | 内容文本 | 
 +----+--------+------------+

而这张表的每一行,可以使用一个javaScript对象来表示

id: 1, title:"标题", content:"内容文本"

ORM就是这样,把关系数据库的结构映射到对象上。而我们只要传入这些结构的javaScript对象,数据库就会自行对应存储数据,是不是简单方便?接下来,我们看看如何使用TypeORM

  • 首先我们需要安装依赖
  • npm install --save @nestjs/typeorm

  • 其次使用TypeORM连接mysql数据库,也需要安装依赖
  • npm install --save typeorm mysql2

  • 首先需要在项目根目录下创建两个文件.env文件和.env.prod文件,存储的分别是开发环境和线上环境不同的环境变量。
  • // 数据库地址
    DB_HOST=localhost  
    // 数据库端口
    DB_PORT=3306
    // 数据库登录名
    DB_USER=root
    // 数据库登录密码
    DB_PASSWD=root
    // 数据库名字
    DB_DATABASE=demo
    

    .env.prod中的是上线要用的数据库信息,如果你的项目要上传到线上管理,为了安全性考虑,建议这个文件添加到.gitignore中。

  • 接着在根目录下创建一个文件夹config(与src同级),然后再创建一个env.ts用于根据不同环境读取相应的配置文件。
  • import * as fs from 'fs';
    import * as path from 'path';
    const isProd = process.env.NODE_ENV === 'production';
    function parseEnv() {
      const localEnv = path.resolve('.env');
      const prodEnv = path.resolve('.env.prod');
      if (!fs.existsSync(localEnv) && !fs.existsSync(prodEnv)) {
        throw new Error('缺少环境配置文件');
      const filePath = isProd && fs.existsSync(prodEnv) ? prodEnv : localEnv;
      return { path:filePath };
    export default parseEnv();
    
  • nest带有环境配置
  • yarn add @nestjs/config
    
  • @nestjs/config 默认会从项目根目录载入并解析.env 文件,从.env文件和process.env合并环境变量键值对,forRoot() 方法注册了 ConfigService 提供者,后者提供了一个 get() 方法来读取这些解析/合并的配置变量。要注入ConfigService,需要在需要使用的地方先导入ConfigModule
  • 而在app.module中使用了ConfigModule.forRoot(),将isGlobal设置为true,在其他地方使用时不需要做任何事,表示全局使用,此时就可以在全局范围内使用process.env.xxx读取全局变量
  • app.module.ts文件中使用@nestjs/config进行全局配置,以及使用@nestjs/typeorm提供的TypeOrmModule连接数据库如下
  • 在app.module.ts使用
  • // app.module.ts
    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    // 环境配置相关
    import { ConfigModule } from '@nestjs/config';
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true
        TypeOrmModule.forRootAsync({
          useFactory: () => ({
            type: 'mysql',
            host: process.env.DATABASE_HOST,
            port: +process.env.DATABASE_PORT, // 来自process.env的每个值都是字符串,前面加+转数字
            username: process.env.DATABASE_USER,
            password: process.env.DATABASE_PASSWORD,
            database: process.env.DATABASE_NAME,
            autoLoadEntities: true, // 自动加载模块 推荐
            // entities: [path.join(__dirname, '/../**/*.entity{.ts,.js}')], // 不推荐
            synchronize: true // 开启同步,生产中要禁止
      controllers: [],
      providers: []
    export class AppModule {}
    
  • 上文代码中forRootAsync使用了TypeORM的异步工程模式,这样可以解决imports的顺序问题,也就是说,使用了forRootAsync,可以不用在意imports这个数组中使用TypeOrmModule的顺序.
  • 因为每个创建的实体必须在连接选项中进行注册,所以TypeORM提供了autoLoadEntities来自动加载创建的数据库实体,使用这种方式也比较推荐,也可以使用entities: [path.join(__dirname, '/../**/*.entity{.ts,.js}')]的方式,这表示,指定包含所有实体的整个目录,该目录下所有实体都将被加载.
  • Entity(实体)

    使用@Entity()来标记通过定义一个新类创建的实体,装饰器根据编写的类,通过映射自动生成一个SQL表,以及他们包含的元数据,其中包含

    Column(普通列)

    @Colimn()来标记普通列

    为列指定类型:

  • @Column("int")/@Column({ type: "int" }):数字类型
  • 还需要指定其他参数:

  • @Column("varchar", { length: 200 }):字符串类型,长度为200
  • @Column({ type: "int", length: 200 }):数字类型,长度为200
  • 列选项定义实体列的其他选项。 你可以在@Column()上指定列选项:

  • title: string: 数据库表中的列名。
  • unique: true:将列标记为唯一列,里面的值不可重复
  • nullable: boolean: 在数据库中使列NULLNOT NULL。 默认情况下,列是nullable:false
  • 更多的请看TypeORM中文文档
  • 每个实体都必须要有一个主列

    @PrimaryColumn()来标记主列,需要给它手动分配值

    @PrimaryGeneratedColumn()来标记主列,该值将使用自动增量值自动生成

    @PrimaryGeneratedColumn('uuid')来标记主列,该值将使用uuid(通用唯一标识符)自动生成,uuid可以被认为是唯一的

  • uuid是让分布式系统中的所有元素都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的uuid。在这样的情况下,就不需考虑数据库创建时的名称重复问题
  • uuid的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-1232个字符,如:550e8400-e29b-41d4-a716-446655440000
  • OneToOne(一对一关系)

  • 使用OneToOne指明一对一的关系,在一对一关系中,表A中的一条记录,只能关联表B中的一条记录。比如:每一个用户都只能有一个用户档案,
  • 在用户表的实体当中使用OneToOne指明一对一的关系,并使用档案表实体作为用户表的某个属性。使用@ JoinColumn定义外键,并允许自定义连接列名和引用的列名。
  • @JoinColumn 必须在且只在关系的一侧的外键上, 设置@JoinColumn在该表的实体中,该表将会包含一个relation id和目标实体表的外键。记住,不能同时在二者的entity中使用。

    OneToMany(一对多关系)

  • 使用OneToMany指明一对多的关系,在一对多关系中,表A中的一条记录,可以关联表B中的一条或多条记录。比如:每一个文章分类都可以对应多篇文章,反过来一篇文章只能属于一个分类,这种分类表和文章表的关系就是一对多的关系。
  • ,即分类表的实体当中使用OneToMany指明一对多的关系,并使用即文章表的实体作为的某个属性。
  • ,即文章表的实体当中使用ManyToOne指明关系,并使用即分类表的实体作为的某个属性。使用@ JoinColumn定义外键,并允许自定义连接列名和引用的列名。
  • TypeORM在处理“一对多”的关系时, 将的主键作为的外键,即@ManyToOne装饰的属性;这样建表时用最少的数据表操作代价,避免数据冗余,提高效率。
  • ManyToMany(多对多关系)

    @ManyToMany()指明多对多关系,多对多是表A的一条记录关联表B的多个记录,而表B的一条记录也可以关联表A的多条记录的关系。比如:一篇文章可以对应多个板块,而一个板块下面也有多个文章。

    @JoinTable用于描述“多对多”关系, @JoinTable()@ManyToMany()关系所必需的, 通过配置joinColumnsinverseJoinColumns来自定义中间表的列名称。

    保存这种关系,需要启动级联cascade

  • @ManyToMany((type) => Role, (role) => role.users, { cascade: true })
  • 使用TypeORM

    进行注册实体,首先我们创建user用户实体。

  • Role是角色实体
  • // user.entity.ts
    import {
      Entity,
      Column,
      JoinTable,
      ManyToMany,
      PrimaryGeneratedColumn,
      CreateDateColumn,
      UpdateDateColumn
    } from 'typeorm';
    import { Role } from './role.entity';
    // @Entity()装饰器自动从所有类生成一个SQL表,以及他们包含的元数据
    // @Entity('users') // sql表名为users
    @Entity() // sql表名为user
    export class User {
      // 主键装饰器,也会进行自增
      @PrimaryGeneratedColumn()
      id: number;
      // 列装饰器
      @Column()
      username: string;
      // @Column('json', { nullable: true }) json格式且可为空
      @Column()
      password: string;
      // 定义与其他表的关系
      // name 用于指定创中间表的表名
      @JoinTable({ name: 'user_roles' })
      // 指定多对多关系
       * 关系类型,返回相关实体引用
       * cascade: true,插入和更新启用级联,也可设置为仅插入或仅更新
       * ['insert']
      @ManyToMany((type) => Role, (role) => role.users, { cascade: true })
      roles: Role[];
      @CreateDateColumn()
      createAt: Date;
      @UpdateDateColumn()
      updateAt: Date;
    
  • 创建角色实体
  • // role.entity.ts
    import {
      Entity,
      Column,
      ManyToMany,
      PrimaryGeneratedColumn,
      CreateDateColumn,
      UpdateDateColumn
    } from 'typeorm';
    import { User } from './user.entity';
    @Entity()
    export class Role {
      @PrimaryGeneratedColumn()
      id: number;
      @Column()
      name: string;
      @ManyToMany((type) => User, (user) => user.roles)
      users: User[];
      @CreateDateColumn()
      createAt: Date;
      @UpdateDateColumn()
      updateAt: Date;
    

    在查找关联表的信息时,需要注意:

  • 使用relations表示需要加载主体,如下是一个查询用户列表所有数据的代码
  • 方式一:relations: { roles: true }

    方式二:relations: ['roles']

    TypeOrm其中有封装好的操作数据库的函数,代码奉上。

    async create(createArticleDto: CreateArticleDto) {
        const article = await this.articleRepository.create({
          ...createArticleDto
        return await this.articleRepository.save(article);
      async getArticleList(paginationsQuery: PaginationQueryDto) {
        const { limit, offset } = paginationsQuery;
        return await this.articleRepository.find({
          skip: offset,
          take: limit
      async findOneById(id: number) {
        return await this.articleRepository.findOneBy({ id });
      async update(id: number, updateArticleDto: UpdateArticleDto) {
        const article = await this.articleRepository.preload({
          ...updateArticleDto
        if (!article) {
          throw new NotFoundException(`${id} not found`);
        return await this.articleRepository.save(article);
      async remove(id: number) {
        const article = await this.articleRepository.findOneBy({ id });
        if (!article) {
          throw new NotFoundException(`${id} not found`);
        return await this.articleRepository.remove(article);
    

    这里我们只是讲解了TypeOrm的数据库连接和基本操作,下一篇我们将会讲到一些更加复杂的管道配置和各种装饰器。

    我是小白,我们一起学习!

    分类:
    前端