Typescript express 新手教程 S9 关系 postgresql的表之间的关系

太长不看

在前面使用MongoDB的时候,如果两个Collection 的数据之间有关系,那么为了告知数据库这些数据之间的关系,需要在创建schema的时候进行,所以说这种Schmea,就是表的原形。
现在更换了postgresql,但是数据关系依然会存在,这里还是在表的原型中设置,配合typeorm,实际上操作是在 entity中进行的。

1对1

当表A中的一行和表B中的一行有关系(这种关系反过来也成立),就是1对1。

src/user/user.entity.ts 
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import Address from '../address/address.entity';
 
@Entity()
class User {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public name: string;
 
  @Column()
  public email: string;
 
  @Column()
  public password: string;
 
  @OneToOne(() => Address)
  @JoinColumn()
  public address: Address;
}
 
export default User;

首先在"拥有"这个关系的entity 中配置 OneToOne,它接受的第一个参数是函数,参数为空,返回有关系的表 的名字

#src/address/address.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
 
@Entity()
class Address {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public street: string;
 
  @Column()
  public city: string;
 
  @Column()
  public country: string;
}
 
export default Address;

而所谓有关系的表,就是 另外一个entity ,这里以Address举例,注意在Address中还没有什么建立关系的操作

让1对1 双向有效

JoinColumn 这个装饰器的作用,就是让“拥有”这个1对1关系的表里边多出一个foreign key,也就是通过foreign key 来获取有关系的另外一个表中的行。

但是有外键还不够,现在1对1的关系没有双向有效,也就是说只有user表"知道"这种关系,address表并不知情,为了让这种关系双向成立,只要在OneToOne里添加第二个参数就行了。

#src/user/user.entity.ts
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import Address from '../address/address.entity';
 
@Entity()
class User {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public name: string;
 
  @Column()
  public email: string;
 
  @Column()
  public password: string;
 
  @OneToOne(() => Address, (address: Address) => address.user)
  @JoinColumn()
  public address: Address;
}
 
export default User;

OneToOne的第二个参数 还是一个函数,参数x为类型是有关系的表 的entity的类名,同时,以返回x.property的形式返回一个值,告诉typeorm有关系的表中的字段是什么。

#src/address/address.entity.ts
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import User from '../user/user.entity';
 
@Entity()
class Address {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public street: string;
 
  @Column()
  public city: string;
 
  @Column()
  public country: string;
 
  @OneToOne(() => User, (user: User) => user.address)
  public user: User;
}
 
export default Address;

现在回到Address,其中 user字段新增了OneToOne的装饰器,很明显,其中参数1和2类似 User中进行了设置,这样,typeorm就能告诉pqsl两个表的两个字段之间的明确1对1关系了。

注意,只在建立1对1关系的两个字段的一边使用JoinColumn,这代表着这个关系的 'owning side',当你去查看数据库的时候,只有owning side的表里边存放着有关系的表的数据的 id,也就是外键。

通过建立双向的关系,好处是可以很轻松的就获取关联表的数据,即便你当前查询的表没有'own'这个关系,但是因为关系是双向的,所有即使查询没有使用JoinColumn修饰的表,另一边的数据也能很容易的获取。

private getAllAddresses = async (request: express.Request, response: express.Response) => {
  const addresses = await this.addressRepository.find({ relations: ['user'] });
  response.send(addresses);
}

通过上方传入 relations的操作,就能反向获取到有关系的表中的数据。
如果这种操作真的足够普遍,甚至可以在构建entity的时候通过配置eager参数来完成自动填充有关系的表的数据这个操作。
类似JoinColumn,两个有关系的表中,只有一边能配置 eager。

#src/user/user.entity.ts
import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import Address from '../address/address.entity';
import Post from '../post/post.entity';
 
@Entity()
class User {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public name: string;
 
  @Column()
  public email: string;
 
  @Column()
  public password: string;
 
  @OneToOne(() => Address, (address: Address) => address.user, {
    cascade: true,
    eager: true,
  })
  @JoinColumn()
  public address: Address;
}
 
export default User;

自动保存有关系的数据

现在还有个问题,address和 user的关系,导致他们的数据是有关联性的,那么就要分别保存,这样太复杂了。

  1. 通过在构建entity的时候传入cascade 选项
  2. 并且在保存user的时候把对应的address的信息嵌入

这样typeorm就能自动把两个表的关联数据都进行更新

1对多和多对1

当表A中的一行和表B中的很多行有关系,但是表B中的一行只和表A中的一行有关系,(比如author 和 post)就形成了1对多(或者多对1)

# src/user/user.entity.ts
import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import Address from '../address/address.entity';
import Post from '../post/post.entity';
 
@Entity()
class User {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public name: string;
 
  @Column()
  public email: string;
 
  @Column()
  public password: string;
 
  @OneToOne(() => Address, (address: Address) => address.user, {
    cascade: true,
    eager: true,
  })
  @JoinColumn()
  public address: Address;
 
  @OneToMany(() => Post, (post: Post) => post.author)
  public posts: Post[];
}
 
export default User;

在user中新增的 posts,OneToMany告诉pqsl这个字段和 Post多一对多关系,具体到post的 author字段

#src/post/post.entity.ts
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import User from '../user/user.entity';
 
@Entity()
class Post {
  @PrimaryGeneratedColumn()
  public id?: number;
 
  @Column()
  public title: string;
 
  @Column()
  public content: string;
 
  @ManyToOne(() => User, (author: User) => author.posts)
  public author: User;
}
 
export default Post;

反过来, 在post中使用ManyToOne,就是告诉pqsl,这个post中的数据和user中的数据有多对一关系,具体到user.posts字段。
注意,使用了manyToOne的一边的表里边存储外键。
oneToMany和manyToOne 不用建立双向关系,因为天生就是双向的,缺一不可,也就是说,可以通过传入relations或者配置eager来完成关联数据的查询

多对多

多对多就是 表A中的一行会和表B中的很多行有关系,反过来也成立。
比如一个帖子可以有多个分类,而一个分类里也会有很多帖子。

#src/post/post.entity.ts
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, ManyToMany, JoinTable } from 'typeorm';
import User from '../user/user.entity';
import Category from "../category/category.entity";
 
@Entity()
class Post {
  @PrimaryGeneratedColumn()
  public id?: number;
 
  @Column()
  public title: string;
 
  @Column()
  public content: string;
 
  @ManyToOne(() => User, (author: User) => author.posts)
  public author: User;
 
  @ManyToMany(() => Category)
  @JoinTable()
  categories: Category[];
}
 
export default Post;
#src/category/category.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
 
@Entity()
class Category {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public name: string;
}
 
export default Category;

注意这里使用了 JoinTable装饰器,这样告诉pqsl创建一个新的表,也就是说 Post和 Category都不用存储这个关系的数据,存在新的表里。
这样,就需要通过 Category控制器来创建几个 category,然后就能回到post控制器来发送带有category的数据了。

而想要获取posts关系的category数据,同样通过传入 relations。

private getAllPosts = async (request: express.Request, response: express.Response) => {
  const posts = await this.postRepository.find({ relations: ['categories'] });
  response.send(posts);
}
 
private getPostById = async (request: express.Request, response: express.Response, next: express.NextFunction) => {
  const id = request.params.id;
  const post = await this.postRepository.findOne(id, { relations: ['categories'] });
  if (post) {
    response.send(post);
  } else {
    next(new PostNotFoundException(id));
  }
}

多对多关系也可以建立 双向关系,但是JoinTable只在一边存在

#src/post/post.entity.ts
import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import Category from '../category/category.entity';
import User from '../user/user.entity';
 
@Entity()
class Post {
  @PrimaryGeneratedColumn()
  public id?: number;
 
  @Column()
  public title: string;
 
  @Column()
  public content: string;
 
  @ManyToOne(() => User, (author: User) => author.posts)
  public author: User;
 
  @ManyToMany(() => Category, (category: Category) => category.posts)
  @JoinTable()
  public categories: Category[];
}
 
export default Post;

#src/category/category.entity.ts
import Post from 'post/post.entity';
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
 
@Entity()
class Category {
  @PrimaryGeneratedColumn()
  public id: string;
 
  @Column()
  public name: string;
 
  @ManyToMany(() => Post, (post: Post) => post.categories)
  public posts: Post[];
}
 
export default Category;

双向关系一旦建立,就能在另外一边通过传入 relations来完成数据(反向的)查询

posted @ 2022-03-09 00:17  刘老六  阅读(106)  评论(0编辑  收藏  举报