当前位置: 首页 > news >正文

第七部分:第五节 - 数据关系与进阶查询 (TypeORM):仓库里复杂的配料组合

现实世界的系统通常涉及多个实体之间复杂的关联,比如一个用户可以有多篇博客文章(一对多),一篇文章可以有多个标签(多对多),一个用户有一个详细的个人资料(一对一)。在关系型数据库中,我们通过外键来建立这些关系。TypeORM 提供了方便的装饰器,让我们可以在实体类中以面向对象的方式定义和管理这些数据库关系。这就像在原材料仓库中,不仅存放了各种原材料,还有详细的配料清单,说明了不同原材料之间如何组合才能做出特定的菜品。

数据库关系类型与 TypeORM 装饰器:

  • 一对一 (One-to-One): 两个实体之间一一对应。例如,一个用户可能有一个且只有一个个人资料。
    • 在拥有外键的一方使用 @OneToOne(() => TargetEntity)@JoinColumn()
    • 在另一方使用 @OneToOne(() => TargetEntity)
  • 一对多 (One-to-Many) / 多对一 (Many-to-One): 一个实体可以关联多个另一个实体,但另一个实体只能关联一个该实体。例如,一个用户可以有多篇博客文章,但一篇博客文章只属于一个用户。
    • 在“多”的一方(外键所在方)使用 @ManyToOne(() => TargetEntity, target => target.propertyName)
    • 在“一”的一方使用 @OneToMany(() => TargetEntity, target => target.propertyName)
  • 多对多 (Many-to-Many): 两个实体之间互相可以关联多个对方。例如,一篇文章可以有多个标签,一个标签可以应用于多篇文章。
    • 在关系的两端都使用 @ManyToMany(() => TargetEntity, target => target.propertyName)
    • 通常需要在其中一端使用 @JoinTable() 来指定连接表(TypeORM 会自动创建一个额外的表来管理多对多关系)。

小例子:定义用户和文章的一对多关系

假设我们有 User 实体和 Post 实体,一个用户有多篇文章。

创建 src/posts/entities/post.entity.ts:

// src/posts/entities/post.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne } from 'typeorm';
import { User } from '../../users/entities/user.entity'; // 导入 User 实体@Entity('posts')
export class Post {@PrimaryGeneratedColumn()id: number;@Column()title: string;@Column('text') // text 类型字段content: string;@CreateDateColumn()created_at: Date;// ManyToOne 关系:多篇文章 (Post) 对应一个用户 (User)@ManyToOne(() => User, user => user.posts) // 第一个参数是目标实体,第二个参数是目标实体中对应关系的属性名author: User; // author 列存储关联的 User 实体对象 (在数据库中会有一个 user_id 外键列)
}

修改 src/users/entities/user.entity.ts

// src/users/entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Unique, OneToMany } from 'typeorm';
import { Post } from '../../posts/entities/post.entity'; // 导入 Post 实体@Entity('users')
export class User {@PrimaryGeneratedColumn()id: number;@Column({ length: 50, unique: true })username: string;@Column({ unique: true, nullable: true })email: string;@Column({ nullable: true })age: number;@CreateDateColumn()created_at: Date;// OneToMany 关系:一个用户 (User) 对应多篇文章 (Post)@OneToMany(() => Post, post => post.author) // 第一个参数是目标实体,第二个参数是目标实体中对应关系的属性名posts: Post[]; // posts 属性将包含该用户的所有 Post 实体数组
}

还需要创建 PostsModule 并在 AppModule 中导入,以及在 PostsModule 中注册 Post 实体。

加载相关数据(延迟加载 vs 急速加载):

当你获取一个包含关系的实体时,TypeORM 默认不会立即加载关联的实体。

  • 延迟加载 (Lazy Loading): 默认行为。关联的实体在第一次访问其属性时才会被加载(返回 Promise)。这可以提高主实体的加载速度。
    const user = await this.usersRepository.findOne({ where: { id: 1 } });
    // user.posts 此时是一个 Promise
    const posts = await user.posts; // 访问 posts 属性时才执行加载文章的数据库查询
    
  • 急速加载 (Eager Loading): 通过在关系装饰器中设置 { eager: true },关联的实体会在加载主实体时立即加载(通过 JOIN 查询)。这会增加主实体的加载时间,但后续访问关联属性无需额外查询。
    // 在 User 实体中的 @OneToMany 装饰器上添加 { eager: true }
    // @OneToMany(() => Post, post => post.author, { eager: true })
    // posts: Post[];const user = await this.usersRepository.findOne({ where: { id: 1 } });
    // user.posts 此时已经加载好了,是一个 Post[] 数组,不是 Promise
    console.log(user.posts);
    

使用 relations 选项加载关系:

即使没有设置 eager: true,你也可以在查询时通过 relations 选项指定要加载的关联关系。这通常比 eager 更灵活,因为你可以按需加载。

// 在 Service 中获取用户及其文章
const userWithPosts = await this.usersRepository.findOne({where: { id: 1 },relations: ['posts'] // 指定加载 posts 关系
});
console.log(userWithPosts.posts); // posts 已经被加载

使用 Query Builder 或原生 SQL 进行进阶查询:

对于复杂的查询、多表 JOIN、聚合查询等,Repository 的简单方法可能不够用。TypeORM 提供了强大的 Query Builder,可以用面向对象的方式构建复杂的查询语句。你也可以直接执行原生 SQL

Query Builder 示例:

// 在 Service 中
async findUsersWithPostCount() {return this.usersRepository.createQueryBuilder('user') // 创建一个查询构建器,指定主实体和别名 'user'.leftJoinAndSelect('user.posts', 'post') // LEFT JOIN user 的 posts 关系,并选择 posts (别名 'post').select(['user.id', 'user.username']) // 选择用户 ID 和用户名.addSelect('COUNT(post.id)', 'postCount') // 计算关联的 post 数量,命名为 postCount.groupBy('user.id') // 按用户 ID 分组.getRawMany(); // 获取原始查询结果 (因为使用了聚合函数)
}// 这是一个稍微复杂的例子,展示了 Query Builder 的能力
// 结果可能是一个 { id: number, username: string, postCount: string } 对象的数组

原生 SQL 示例:

// 在 Service 中
async findUsersByNativeSql(minAge: number) {const query = `SELECT * FROM users WHERE age > ?`;const [rows] = await this.usersRepository.query(query, [minAge]); // 执行原生 SQLreturn rows; // 返回原始结果数组
}

原生 SQL 非常灵活,但缺乏 TypeORM 的类型安全和实体映射能力,应谨慎使用。

小结: 在 TypeORM 中,可以使用装饰器 (@OneToOne, @OneToMany, @ManyToOne, @ManyToMany) 定义实体之间的关系。通过配置 { eager: true } 或使用 relations 选项来加载关联数据。对于复杂的查询,可以使用 Query Builder 或执行原生 SQL。

练习:

  1. 在你的 NestJS 项目中,为你的资源(博客文章或任务)定义实体之间的关系。例如,如果你选择博客文章,可以定义 AuthorPost 实体,并建立一对多关系(一个作者有多篇文章)。
  2. 修改相应的实体文件,添加关系装饰器。
  3. 修改 Service 中的方法,例如获取作者详情时,使用 relations: ['posts'] 选项同时加载该作者的所有文章。
  4. (进阶)尝试使用 Query Builder 编写一个查询,例如:
    • 获取所有作者及其文章数量。
    • 获取发布年份在某个范围内的所有书籍(如果你选择了书籍项目)。
  5. 运行应用,测试你带有关系加载的 API 端点,确认能够正确获取关联数据。
http://www.xdnf.cn/news/766171.html

相关文章:

  • 第1篇:数据库中间件概述:架构演进、典型方案与应用场景
  • 微服务常用日志追踪方案:Sleuth + Zipkin + ELK
  • SCAU8642--快速排序
  • C++ 内存泄漏检测器设计
  • 7.文本内容处理sort,uniq,out,cat,comm,diff
  • NX869NX874美光固态颗粒NX877NX883
  • [HTML5]快速掌握canvas
  • 在 Linux 服务器上无需 sudo 权限解压/打包 .7z 的方法
  • C++ - 数据处理之数值转不同进制的字符串(数值转十进制字符串、数值转八进制字符串、数值转二进制字符串、数值转十六进制字符串)
  • 黑马程序员C++核心编程笔记--4 类和对象--多态
  • 《信号与系统》--期末总结V1.0
  • linux 的devmem2 调式使用说明
  • Vue-3-前端框架Vue基础入门之VSCode开发环境配置和Tomcat部署Vue项目
  • 常见ADB指令
  • Vue-4-前端框架Vue基础入门之Vue的常用操作
  • opencv调用模型
  • 渗透实战PortSwigger Labs AngularJS DOM XSS利用详解
  • 【MySQL】视图与用户管理
  • linux——文件系统
  • 使用API网关Kong配置反向代理和负载均衡
  • IoTGateway项目生成Api并通过swagger和Postman调用
  • Fisher准则例题——给定类内散度矩阵和类样本均值
  • 数据库系统概论(十六)数据库安全性(安全标准,控制,视图机制,审计与数据加密)
  • 好用的C/C++/嵌入式 IDE: CLion的下载安装教程(保姆级教程)
  • 专注成就技术传奇:一路向前的力量
  • 设备驱动与文件系统:03 生磁盘的使用
  • Android高级开发第三篇 - JNI异常处理与线程安全编程
  • HarmonyOS鸿蒙Taro跨端框架
  • STM32CubeDAC及DMA配置
  • 高效微调方法简述