TypeORM框架中的 多种复杂关系

638 阅读5分钟

下面设计许多装饰器,可以先熟悉一下,更好的理解下面的关系

点击跳转装饰器

在 TypeORM 中,一般使用使用装饰器来定义实体之间的关系

要想连接两个实体,可以在表中指定一个列来明确他们之间的关系,这个列叫做外键列

@JoinColumn() - 用来指定关系的外键列

通过指定外键列,可以在关系中明确指定哪个列用于连接两个实体,从而更加精细地控制实体之间的关系。

外键列是一个用于连接两个实体的列,它存储了另一个实体的主键或唯一标识符。

设置@JoinColumn的哪一方,哪一方的表将包含一个"relation id"和目标实体表的外键。

// 实体
@Entity()
class User {
  // 主键
  @PrimaryGeneratedColumn()
  id: number;
  // 普通键
  @Column()
  name: string;
  // 定义一对一关系
  @OneToOne(type => Profile)
  // 指定关系的外键列
  @JoinColumn()
  profile: Profile;
}

@Entity()
class Profile {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  age: number;
  // 指向 user 中的 profile 列,表示两个实体之间的关系是双向的
  @OneToOne(type => User, user => user.profile)
  user: User;
}

外键列名默认为${propertyName}_id,即profile_id,可以通过在 @JoinColumn() 装饰器中传递参数来指定外键列名。

@Entity()
class User {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  name: string;
  @OneToOne(type => Profile)
  @JoinColumn({ name: "my_profile_id" })
  profile: Profile;
}

使用 @JoinColumn({ name: "my_profile_id" }) 来指定外键列名为 my_profile_id,而不是默认的 profile_id

Join 列始终是对其他一些列的引用,默认情况下,关系始终引用相关实体的主列,但是也可以指定引用其他列,@JoinColumn对象参数包含一个 referencedColumnName 属性,用于指定引用列的名称,例如:

@Entity()
class Product {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  name: string;
  // 多个 `Product` 实体 对应一个 `Category`实体
  @ManyToOne(type => Category)
  // 使用 `referencedColumnName` 属性来指定引用列的名称为 `name`
  @JoinColumn({ referencedColumnName: "name" })
  category: Category;
}
@Entity()
class Category {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  name: string;
  //  // 一个 `Category` 实体 拥有多个 `Product`实体
  @OneToMany(type => Product, product => product.category)
  // 因为拥有多个 `Product`实体,所以存储的是数组
  products: Product[];
}

@JoinTable - 创建一个联结表,引用相关实体

@JoinTable() 装饰器用于定义多对多关系的联结表。联结表是一个中间表,用于存储多对多关系中实体之间的关联关系。

@JoinTable() 装饰器中,可以传递一个对象参数,该对象参数包含一个 name 属性,用于指定联结表的表名,以及 joinColumninverseJoinColumn 属性,用于指定联结表中的外键列。

@Entity()
class Question {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  title: string;
  @ManyToMany(type => Category)
  // 联结表
  @JoinTable({
    // 设置表名
    name: "question_categories",
    // 指定联结表中的外键列
    joinColumn: {
      name: "question",
      referencedColumnName: "id"
    },
    inverseJoinColumn: {
      name: "category",
      referencedColumnName: "id"
    }
  })
  categories: Category[];
}
@Entity()
class Category {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  name: string;
  @ManyToMany(type => Question, question => question.categories)
  questions: Question[];
}

joinColumn 属性指定了 question 列为联结表中的外键列,引用了 Question 实体的 id

inverseJoinColumn 属性指定了 category 列为联结表中的外键列,引用了 Category 实体的 id 列。

一对一关系

@OneToOne():定义一对一关系

一对一关系:指两个实体之间的关系是唯一的。例如,一个人只有一个身份证号,一个身份证号也只属于一个人。

例如:

关系可以是单向的和双向的,两侧都有@OneToOne才是双向,一侧有是单向

import { Entity, PrimaryGeneratedColumn, Column, OneToOne } from "typeorm";
import { User } from "./User";

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

  @Column()
  gender: string;

  @Column()
  photo: string;

  @OneToOne(() => User, user => user.profile) // 将另一面指定为第二个参数
  user: User;
}
import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from "typeorm";
import { Profile } from "./Profile";

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

  @Column()
  name: string;

  @OneToOne(() => Profile, profile => profile.user) // 指定另一面作为第二个参数
  @JoinColumn()
  profile: Profile;
}

注意:

@JoinColumn必须只在关系的一侧且拥有外键的表上。

拥有外键:就是@Column()普通键,不能只有主键

// 使用
const profiles = await connection
  .getRepository(Profile)
  .createQueryBuilder("profile")
  .leftJoinAndSelect("profile.user", "user")
  .getMany();

一对多关系

@OneToMany():定义一对多关系

@ManyToOne可以单独使用,但@OneToMany必须搭配@ManyToOne使用

一对多关系:指一个实体可以对应多个另一个实体。例如,一个学校可以有多个学生,但一个学生只能在一个学校就读。

User 可以拥有多张 photos,但每张 photo 仅由一位 user 拥有

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { User } from "./User";

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

  @Column()
  url: string;

  @ManyToOne(() => User, user => user.photos)
  user: User;
}
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Photo } from "./Photo";

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

  @Column()
  name: string;

  @OneToMany(() => Photo, photo => photo.user)
  photos: Photo[];
}

结果:

在你设置@ManyToOne的地方,相关实体将有"关联 id"和外键。

+-------------+--------------+----------------------------+
|                         photo                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| url         | varchar(255) |                            |
| userId      | int(11)      |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|                          user                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
+-------------+--------------+----------------------------+

使用:

const users = await connection
  .getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.photos", "photo")
  .getMany();

// or from inverse side

const photos = await connection
  .getRepository(Photo)
  .createQueryBuilder("photo")
  .leftJoinAndSelect("photo.user", "user")
  .getMany();

多对一关系

@ManyToOne():定义多对一关系

多对一关系:指多个实体可以对应一个另一个实体。例如,多个学生可以在同一个学校就读。

@ManyToOne可以单独使用,但@OneToMany必须搭配@ManyToOne使用

多对多关系

@ManyToMany():定义多对多关系

@JoinTable()@ManyToMany关系所必需的,必须把@JoinTable放在关系的一个(拥有)方面,只有一个有,ueng两个同时都存在。

多对多关系:指多个实体之间相互关联。例如,多个学生可以选择同一门课程,同一门课程也可以有多个学生选择。

单向、双向都是可以的

单向是仅在一侧与关系装饰器的关系。

双向是与关系两侧的装饰者的关系。

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm";
import { Question } from "./Question";

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

  @Column()
  name: string;

  @ManyToMany(() => Question, question => question.categories)
  questions: Question[];
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from "typeorm";
import { Category } from "./Category";

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

  @Column()
  title: string;

  @ManyToMany(() => Category)
  @JoinTable()
  categories: Category[];
}

结果:

+-------------+--------------+----------------------------+
|                        category                         |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|                        question                         |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| title       | varchar(255) |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|              question_categories_category               |
+-------------+--------------+----------------------------+
| questionId  | int(11)      | PRIMARY KEY FOREIGN KEY    |
| categoryId  | int(11)      | PRIMARY KEY FOREIGN KEY    |
+-------------+--------------+----------------------------+

使用:

const categoriesWithQuestions = await connection
  .getRepository(Category)
  .createQueryBuilder("category")
  .leftJoinAndSelect("category.questions", "question")
  .getMany();