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的关系,导致他们的数据是有关联性的,那么就要分别保存,这样太复杂了。
- 通过在构建entity的时候传入cascade 选项
- 并且在保存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来完成数据(反向的)查询