一、解决场景
有两张表,分别是Menu
和Page
,一个menu
下会关联有多个page
,如下图:
要实现这种一对多的关系,我们要使用typeorm
的@OneToMany
和@ManyToOne
这两个装饰器,具体如下:
- 在
Menu
实体类(entity)中使用@OneToMany
,申明关联到多个Page
实例。 - 在
Page
实体类(entity)中使用@ManyToOne
,同时申明关联到一个Menu
实例。
二、实现步骤
1、Menu实体类(menu.entity.ts)
有一张Menu
表,代码如下:
import { Page } from '@/module/page/entities/page.entity';
import { ApiProperty } from '@nestjs/swagger';
import {
Column,
CreateDateColumn,
Entity,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn
} from 'typeorm';
@Entity({
name: 'menu',
})
export class Menu {
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
description: '菜单名称',
required: true,
})
@Column({
name: 'name',
length: 255,
comment: '菜单名称',
})
name: string;
@ApiProperty({
description: '是否叶子节点',
required: true,
})
@Column({
name: 'is_leaf',
default: true,
comment: '是否叶子节点',
})
isLeaf: boolean;
@ApiProperty({
description: '外链或页面唯一标识',
required: true,
})
@Column({
name: 'link',
default: "",
length: 255,
comment: '外链或页面唯一标识',
})
link: string;
@ApiProperty({
description: '打开页面方式',
required: true,
})
@Column({
name: 'target',
default: '_self',
length: 50,
comment: '打开页面方式',
})
target: string;
@ApiProperty({
description: '菜单图标',
})
@Column({
name: 'icon',
length: 50,
comment: '菜单图标',
})
icon: string;
@ApiProperty({
description: '父菜单Id',
})
@Column({
name: 'parent_id',
default: 0,
comment: '父菜单Id',
})
parentId: number;
@ApiProperty({
description: '菜单路径',
})
@Column({
name: 'parent_arr',
default: "0",
comment: '菜单路径',
})
parentArr: string;
@ApiProperty({
description: '所属站点Id',
})
@Column({
name: 'site_id',
comment: '所属站点Id',
})
siteId: number;
@ApiProperty({
description: '是否隐藏',
})
@Column({
name: 'is_hide',
default: false,
type: 'boolean',
comment: '是否隐藏',
})
isHide: boolean;
@CreateDateColumn({
name: 'create_time',
type: 'timestamp',
select: false,
comment: '创建时间',
})
createTime: Date;
@UpdateDateColumn({
name: 'update_time',
type: 'timestamp',
select: false,
comment: '修改时间',
})
updateTime: Date;
@OneToMany((type) => Page, (p) => p.menu)
pages: Page[];
}
2、Page实体类(page.entity.ts)
import { Menu } from '@/module/menu/entities/menu.entity';
import { ApiProperty } from '@nestjs/swagger';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
VersionColumn,
} from 'typeorm';
@Entity({
name: 'page',
})
export class Page {
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
description: '页面名称',
required: true,
})
@Column({
name: 'name',
length: 255,
comment: '页面名称',
})
name: string;
@ApiProperty({
description: '所属站点标识',
})
@Column({
name: 'siteid',
nullable: true,
comment: '所属站点ID'
})
siteid: number;
@ApiProperty({
description: '所属站点名字',
})
@Column({
name: 'site_name',
nullable: true,
comment: '所属站点名字'
})
siteName: string;
@ApiProperty({
description: '页面Schema数据内容',
})
@Column({
name: 'schema_content',
nullable: true,
comment: '页面Schema数据内容',
type:"text"
})
schemaContent: string;
@ApiProperty({
description: '删除',
})
@Column({
name: 'is_delete',
nullable: true,
default: false,
comment: '删除',
})
isDelete: boolean;
@ApiProperty({
description: '创建人ID',
})
@Column({
name: 'create_user_id',
nullable: false,
comment: '创建人ID',
})
createUserId: number;
@ApiProperty({
description: '创建人',
})
@Column({
name: 'create_user_nickname',
nullable: false,
comment: '创建人昵称',
})
createUserNickName: string;
@CreateDateColumn({
name: 'create_time',
type: 'datetime',
// select: false,
nullable: true,
comment: '创建时间',
})
createTime: Date;
@ApiProperty({
description: '更新人ID',
})
@Column({
name: 'update_user_id',
nullable: true,
comment: '更新人ID',
})
updateUserId: number;
@ApiProperty({
description: '更新人',
})
@Column({
name: 'update_user_nickname',
nullable: true,
comment: '更新人昵称',
})
updateUserNickName: string;
@UpdateDateColumn({
name: 'update_time',
type: 'datetime',
// select: false,
nullable: true,
comment: '修改时间',
})
updateTime: Date;
@ManyToOne((type) => Menu, (c) => c.pages)
menu?: Menu;
}
按照上述的menu.entity.ts
和page.entity.ts
实体类申明,会自动创建好menu
和page
这两张表,并且会自动在page
中创建menuId
外键字段。
3、创建数据
//---->创建page
//1、创建page实例
const page = new Page();
page.name = "页面";
//2、创建menu实例
const menu = new Menu();
menu.id = page?.menuId;
//3、赋值给page.menu
page.menu = menu;
//4、开启保存事务
await this.pageRepository.save(page);
return true;
//---->创建menu
//1、创建menu实例
const menu = new Menu();
menu.name = "菜单一";
//2、创建page1、page2实例
const page1 = new Page();
page1.name = "页面1";
const page2 = new Page();
page2.name = "页面2";
//2、赋值给menu.pages
menu.pages = [page1, page2]
//3、开启保存事务
await this.menuRepository.save(menu);
更多保存方式,请查看typeorm官网(many-to-one-one-to-many-relations)
4、数据查询
//查询page列表
let [data, total] = await this.pageRepository.findAndCount({
skip: (current - 1) * pageSize,
take: pageSize,
order: { [sortBy]: sortOrder },
relations: {
menu: true,
},
select: {
'id': true,
'name': true,
'createUserId': true,
'createUserNickName': true,
'createTime': true,
'updateUserId': true,
'updateUserNickName': true,
'updateTime': true,
'menu': {
'name': true,
}
},
where,
});
返回的数据结构如下:
可以看到,返回的page列表数据中,对应的menu数据单独包裹返回。
更多查询方式,请查看typeorm官网(find-options)
注意:在使用typeorm必须要知道背后执行的sql是什么?才能对写法做优化。这里贴一张上述写法某次访问日志打印的sql,可以看到里面用到left join
,所以还是要谨慎使用实体关联,合理设计数据库结构~~~