Bootstrap

【Nestjs实操】typeorm一对多如何配置

一、解决场景

有两张表,分别是MenuPage,一个menu下会关联有多个page,如下图:

image-20240331225216836

要实现这种一对多的关系,我们要使用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.tspage.entity.ts实体类申明,会自动创建好menupage这两张表,并且会自动在page中创建menuId外键字段。

image-20240331230436223

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,
});

返回的数据结构如下:

image-20240331231416840

可以看到,返回的page列表数据中,对应的menu数据单独包裹返回。
更多查询方式,请查看typeorm官网(find-options)

注意:在使用typeorm必须要知道背后执行的sql是什么?才能对写法做优化。这里贴一张上述写法某次访问日志打印的sql,可以看到里面用到left join,所以还是要谨慎使用实体关联,合理设计数据库结构~~~
在这里插入图片描述

;