使用 TypeORM + Nest.js 定义数据库模型

设计数据库模型是一个涉及数据组织、结构化和存储的过程。这个过程确保了数据的完整性、减少了冗余,并且提高了查询效率。以下是设计数据库模型的步骤:

1. 确认有哪些实体

实体是现实世界中可以区分的对象或事物,它们通常对应于数据库中的表。在确定实体时,你需要识别出业务流程中的主要组件。例如,如果你正在设计一个电子商务数据库,实体可能包括"客户"、"产品"、"订单"等。

2. 确认有哪些相关的实体

实体之间往往存在关联,这些关联定义了实体如何相互关联。例如,在电子商务数据库中,"客户"可能与"订单"有关联,因为客户会下订单。同样,"订单"与"产品"有关联,因为订单包含一个或多个产品。

3. 确认关联关系

关联关系描述了实体之间的交互方式,这些关系可以是一对一、一对多或多对多。例如,"客户"和"订单"之间通常是一对多的关系,因为一个客户可以有多个订单,但每个订单只能属于一个客户。确定关联关系是设计数据库模型中非常关键的一步,因为它影响了数据的整合和查询的复杂性。

4. 确认实体属性

每个实体都会有一系列的属性,这些属性描述了实体的特征。例如,对于"客户"实体,属性可能包括客户ID、姓名、地址和电子邮箱等。在确定属性时,重要的是要确保属性的原子性,即每个属性都是不可分割的基本单位。

5. 不断优化细节

数据库模型的设计是一个迭代过程,需要不断地优化。这包括正规化过程,以减少数据冗余和提高数据一致性。正规化通常涉及到将一个大表分解成多个小表,并通过外键关联。此外,还需要考虑索引的使用以提高查询效率,以及如何处理事务、并发和安全性问题。

示例场景:图书馆管理系统

在软件设计和数据库建模中,从用户角色出发来确认实体及其相关实体、关联关系和实体属性是一个常见的方法,这通常涉及到用户故事或用例分析。下面通过一个例子来说明这个过程:

假设我们要设计一个图书馆管理系统,我们的用户角色包括图书管理员和读者。

步骤 1:定义用户故事或用例

首先,我们需要定义用户故事或用例来描述用户角色的需求。例如:

  • 作为一个图书管理员,我想要添加新书籍,以便扩充图书馆的藏书。
  • 作为一个读者,我想要查找图书,以便借阅我感兴趣的书籍。

步骤 2:从用例中识别实体

接下来,我们从每个用户故事或用例中识别出涉及的实体。

  • 图书管理员用例 涉及的实体可能有:
    • 图书(Book)
  • 读者用例 涉及的实体可能有:
    • 图书(Book)
    • 读者(Reader)

步骤 3:确认关联关系

最后,我们需要确认实体之间的关联关系。

  • **图书(Book) 读者(Reader)**之间的关系可能是:
    • 读者可以借阅多本图书(一对多关系:一个 Reader 可以有多个 Book)
    • 一本图书可以被多个读者借阅(多对一关系:一个 Book 可以被多个 Reader 借阅)

步骤 4:定义实体属性

然后为每个实体定义属性。

  • **图书(Book)**的属性可能包括:
    • 书名(Title)
    • 作者(Author)
    • ISBN 编号(ISBN)
    • 出版日期(Publication Date)
    • 类别(Genre)
  • **读者(Reader)**的属性可能包括:
    • 姓名(Name)
    • 读者编号(Reader ID)
    • 借阅记录(Borrowing Record)

步骤 5:绘制实体关系图(ERD)

根据上述信息,我们可以绘制一个实体关系图(ERD),来可视化实体、属性和关系。

rust 复制代码
[Reader] --<借阅>-- [Book]
|         |
|         |-> 姓名(Name)
|         |-> 读者编号(Reader ID)
|         |-> 借阅记录(Borrowing Record)
|
|-> 书名(Title)
|-> 作者(Author)
|-> ISBN编号(ISBN)
|-> 出版日期(Publication Date)
|-> 类别(Genre)

通过这个过程,我们从用户角色和使用路径出发,识别了系统中的关键实体(图书和读者),实体之间的关联关系(如借阅关系)以及它们的属性(如书名、作者、读者编号等)。这样的分析有助于我们构建一个结构化的数据库模型,从而支持图书馆管理系统的开发。

使用 TypeORM 生成实体和属性

TypeORM 是一个开源的对象关系映射(ORM)库,使用函数的操作映射数据库底层的 sql 执行。所以使得开发人员不必学习复杂的 sql 语句,从而操作数据库。

TypeORM 支持多种数据库,包括 MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL 等。

在 Nest.js 中使用 TypeORM 定义 MySQL 数据库表结构并完成 CRUD(创建、读取、更新、删除)操作需要几个步骤。下面是一个简化的流程:

1. 安装必要的包

首先,安装 Nest.js CLI(如果还没有安装的话)和必要的 npm 包:

shell 复制代码
npm i -g @nestjs/cli
npm i @nestjs/typeorm typeorm mysql2

这里 mysql2 是 MySQL 的 Node.js 驱动。

2. 创建数据库连接

app.module.ts 或相关的模块文件中配置 TypeORM:

typescript 复制代码
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'your_username',
      password: 'your_password',
      database: 'your_database',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true, // 注意:在生产环境中不建议使用此选项,因为它可能会导致数据丢失
    }),
    // ... other modules
  ],
  // ... controllers, providers
})
export class AppModule {}

3. 定义实体

创建一个 TypeScript 类作为实体,并使用装饰器定义表结构:

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

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

  @Column({ length: 500 })
  name: string;

  @Column('text')
  description: string;

  // 其他列...
}

4. 创建服务

创建一个服务来处理 CRUD 操作:

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

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

  findAll(): Promise<User[]> {
    return this.userRepository.find();
  }

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

  async create(user: User): Promise<User> {
    return this.userRepository.save(user);
  }

  async update(id: string, user: User): Promise<void> {
    await this.userRepository.update(id, user);
  }

  async delete(id: string): Promise<void> {
    await this.userRepository.delete(id);
  }
}

5. 创建控制器

创建一个控制器来处理 HTTP 请求:

typescript 复制代码
import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  getAll(): Promise<User[]> {
    return this.userService.findAll();
  }

  @Get(':id')
  getOne(@Param('id') id: string): Promise<User> {
    return this.userService.findOne(id);
  }

  @Post()
  create(@Body() user: User): Promise<User> {
    return this.userService.create(user);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() user: User): Promise<void> {
    return this.userService.update(id, user);
  }

  @Delete(':id')
  delete(@Param('id') id: string): Promise<void> {
    return this.userService.delete(id);
  }
}

6. 注册模块

在你的模块(例如 app.module.ts)中注册 TypeOrmModule.forFeature 和控制器:

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

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

这些步骤概述了在 Nest.js 应用程序中使用 TypeORM 完成 CRUD 操作的基本流程。

具体实现可能会根据具体需求有所不同。

在使用 TypeORM 结合 Nest.js 定义表结构时,会用到一系列的装饰器(decorators)来定义实体(Entity)和表之间的映射关系。以下是一些常用的 TypeORM 装饰器:

  1. @Entity()
    用于定义一个类作为数据库表的映射。
  2. @PrimaryGeneratedColumn()
    将某个字段标记为主键,并且值会自动生成。通常用于自增的ID字段。
  3. @Column()
    定义表中的一个列。可以指定列的类型、长度、是否唯一等属性。
  4. @CreateDateColumn()
    自动设置列的值为插入数据的时间。
  5. @UpdateDateColumn()
    自动设置列的值为数据更新的时间。
  6. @DeleteDateColumn()
    用于软删除功能,自动设置列的值为数据被软删除的时间。
  7. @VersionColumn()
    用于乐观锁,每次实体更新时,该列的值会自动增加。
  8. @ManyToOne(), @OneToMany(), @OneToOne(), @ManyToMany()
    这些装饰器用于定义不同类型的关联关系。
  9. @JoinColumn()
    用于@OneToOne()和@ManyToOne()关系,指定外键列。
  10. @JoinTable()
    用于@ManyToMany()关系,指定连接表的信息。
  11. @Index()
    用于为实体的某个或某些列创建数据库索引。
  12. @Unique()
    用于定义表级别的唯一约束。
  13. @Check()
    用于定义表级别的检查约束。
  14. @Generated()
    用于定义一个生成的列,如自动创建的UUID。
  15. @EntityRepository()
    用于自定义仓库类,虽然不直接定义表结构,但与表操作紧密相关。

在依赖注入 TypeORM 后,TypeORM 提供了一系列的方法来操作数据库,这些方法通常通过 Repository 对象来调用。Repository 是 TypeORM 的抽象,它允许你执行常见的持久层操作。以下是一些常用的 Repository 方法:

这些方法可以大致分为以下几类:

  1. Repository 方法 - 用于对单个数据库实体进行操作的方法,包括:
    • find:查找多个实体。
    • findOne:查找单个实体。
    • create:根据提供的对象创建一个新的实体实例,但不保存它。
    • save:保存给定的实体或实体数组。
    • remove:从数据库中移除给定的实体。
    • update:更新数据库中的实体。
    • insert:向数据库中插入新的实体。
    • delete:删除数据库中的记录。
    • count:返回满足条件的实体数量。
    • increment:增加实体的某个列的值。
    • decrement:减少实体的某个列的值。
  2. QueryBuilder - 用于构建更复杂查询的接口,例如:
    • createQueryBuilder:创建一个新的查询构建器。
    • select:选择特定的列。
    • where:添加查询条件。
    • orderBy:添加排序条件。
    • leftJoinAndSelect:添加左连接并选择相关实体。
    • getMany:执行查询并返回多个结果。
    • getOne:执行查询并返回单个结果。
  3. Entity Manager - 提供了一些更底层的数据库操作方法,例如:
    • transaction:执行事务操作。
    • createQueryRunner:创建一个可以用来管理单个数据库连接的查询运行器。
    • getCustomRepository:获取自定义的 repository。
  4. Migration 方法 - 用于数据库迁移,例如:
    • createMigration:创建新的迁移文件。
    • runMigration:执行迁移。
    • revertMigration:回滚迁移。
  5. Subscriber 和 Listener - 允许你监听和订阅特定的数据库事件,例如:
    • beforeInsert:在插入之前触发。
    • afterInsert:在插入之后触发。
    • beforeUpdate:在更新之前触发。
    • afterUpdate:在更新之后触发。
    • beforeRemove:在删除之前触发。
    • afterRemove:在删除之后触发。

以下是使用 TypeORM 进行数据库操作的一些代码示例,基于 Nest.js 框架:

  • 查找记录
typescript 复制代码
async findAllUsers(): Promise<User[]> {
  return await this.userRepository.find();
}

async findOneUser(id: number): Promise<User> {
  return await this.userRepository.findOne(id);
}
  • 创建和保存记录
typescript 复制代码
async createUser(userData: Partial<User>): Promise<User> {
  const user = this.userRepository.create(userData);
  return await this.userRepository.save(user);
}
  • 更新记录
typescript 复制代码
async updateUser(id: number, updateData: Partial<User>): Promise<void> {
  await this.userRepository.update(id, updateData);
}
  • 删除记录
typescript 复制代码
async deleteUser(id: number): Promise<void> {
  await this.userRepository.delete(id);
}
  • 使用 QueryBuilder 进行复杂查询
typescript 复制代码
async findUsersWithPosts(): Promise<User[]> {
  return this.userRepository
    .createQueryBuilder('user') // 'user' 是给 User 实体起的别名
    .leftJoinAndSelect('user.posts', 'post') // 假设 User 实体有一个 posts 关联
    .where('user.isActive = :isActive', { isActive: true })
    .andWhere('post.published = :published', { published: true })
    .orderBy('user.createdAt', 'DESC')
    .getMany();
}

// 对于更复杂的查询,你可以使用子查询和多种不同的条件。例如:
async findUsersWithRecentPosts(): Promise<User[]> {
  return this.userRepository
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.posts', 'post')
    .where('user.isActive = :isActive', { isActive: true })
    .andWhere(new Brackets(qb => {
      qb.where('post.createdAt > :date', { date: new Date(/* 一些日期 */) })
        .orWhere('post.title LIKE :title', { title: '%特定标题%' });
    }))
    .getMany();
}
// 我们使用了 Brackets 来创建一个嵌套的条件块,这允许我们在一个子查询中组合多个条件。
  • 使用 Entity Manager 进行事务操作
typescript 复制代码
import { EntityManager } from 'typeorm';

async updateUserAndLogTransaction(user: User, logMessage: string): Promise<void> {
  await this.userRepository.manager.transaction(async (entityManager: EntityManager) => {
    await entityManager.save(user);
    await entityManager.insert(Log, { message: logMessage });
  });
}
相关推荐
2401_8576226616 分钟前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
正小安20 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
2402_8575893620 分钟前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没1 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式