Skip to content

Many To Many 关联

介绍

Many To Many 关联表示两个实体之间存在多对多的关系。这种关联需要通过中间表(也称为连接表或枢纽表)来维护关系,每个实体都可以拥有另一个实体的多个实例。

定义 Many To Many 关联

使用 @ManyToMany 装饰器来定义多对多关联:

ts
import { Table, Column, PrimaryKey, ManyToMany } from '@ibestservices/ibest-orm';

@Table()
class User {
  @PrimaryKey()
  id?: number;

  @Column()
  name!: string;

  @Column()
  email?: string;

  @ManyToMany(() => Role, {
    through: 'user_roles',
    throughForeignKey: 'user_id',
    throughOtherKey: 'role_id'
  })
  roles?: Role[];
}

@Table()
class Role {
  @PrimaryKey()
  id?: number;

  @Column()
  name!: string;

  @Column()
  description?: string;

  @ManyToMany(() => User, {
    through: 'user_roles',
    throughForeignKey: 'role_id',
    throughOtherKey: 'user_id'
  })
  users?: User[];
}

配置参数

target

  • 类型: () => Class
  • 描述: 目标模型类的工厂函数
  • 必需: 是

through

  • 类型: string
  • 描述: 中间表的名称
  • 必需: 是
  • 示例: 'user_roles', 'article_tags'

throughForeignKey

  • 类型: string
  • 描述: 中间表中指向当前模型的外键字段名
  • 必需: 是
  • 示例: 'user_id', 'article_id'

throughOtherKey

  • 类型: string
  • 描述: 中间表中指向目标模型的外键字段名
  • 必需: 是
  • 示例: 'role_id', 'tag_id'

中间表

多对多关联需要一个中间表来存储关联关系。IBest ORM 会在 migrate() 时自动创建中间表

typescript
// 调用 migrate 时会自动创建中间表
orm.migrate(User, Role);

// IBest ORM 会自动创建 user_roles 表,结构如下:
// CREATE TABLE IF NOT EXISTS user_roles (
//   user_id INTEGER NOT NULL,
//   role_id INTEGER NOT NULL,
//   PRIMARY KEY (user_id, role_id)
// )

自动表创建

调用 migrate() 时,IBest ORM 会自动扫描实体的 ManyToMany 关联配置,并创建对应的中间表:

  • 两个外键字段(由 throughForeignKeythroughOtherKey 指定)
  • 复合主键约束(确保关联唯一性)

查询 Many To Many 关联

预加载关联数据

ts
// 查询用户及其角色
const usersWithRoles = orm.query(User)
  .with('roles')
  .where({ id: 1 })
  .find();

console.log('用户及角色:', usersWithRoles[0]);
// 输出:
// {
//   id: 1,
//   name: "张三",
//   email: "zhangsan@example.com",
//   roles: [
//     {
//       id: 1,
//       name: "admin",
//       description: "管理员"
//     },
//     {
//       id: 2,
//       name: "editor",
//       description: "编辑"
//     }
//   ]
// }

反向查询

ts
// 查询角色及其用户
const rolesWithUsers = orm.query(Role)
  .with('users')
  .where({ id: 1 })
  .find();

console.log('角色及用户:', rolesWithUsers[0]);
// 输出:
// {
//   id: 1,
//   name: "admin",
//   description: "管理员",
//   users: [
//     {
//       id: 1,
//       name: "张三",
//       email: "zhangsan@example.com"
//     },
//     {
//       id: 3,
//       name: "王五",
//       email: "wangwu@example.com"
//     }
//   ]
// }

查询所有关联数据

ts
// 查询所有用户及其角色
const usersWithRoles = orm.query(User)
  .with('roles')
  .find();

usersWithRoles.forEach(user => {
  const roleNames = user.roles?.map(role => role.name).join(', ') || '无角色';
  console.log(`${user.name}: ${roleNames}`);
});

注意事项

级联操作

ManyToMany 关联支持级联创建、更新和删除操作:

typescript
import { Table, Column, PrimaryKey, ManyToMany, CascadeType } from '@ibestservices/ibest-orm';

@Table()
class Article {
  @PrimaryKey()
  id?: number;

  @Column()
  title!: string;

  @ManyToMany(() => Tag, {
    through: 'article_tags',
    throughForeignKey: 'article_id',
    throughOtherKey: 'tag_id',
    cascade: [CascadeType.Create, CascadeType.Update, CascadeType.Delete]
  })
  tags?: Tag[];
}

@Table()
class Tag {
  @PrimaryKey()
  id?: number;

  @Column()
  name!: string;
}

// 级联创建:自动创建文章、标签和中间表关联
const article = new Article();
article.title = 'TypeScript 入门';
article.tags = [
  Object.assign(new Tag(), { name: '技术' }),
  Object.assign(new Tag(), { name: '教程' })
];
orm.insertWithRelations(article);

// 级联更新:同步关联关系(删除旧关联,创建新关联)
article.tags = [
  Object.assign(new Tag(), { name: '前端' }),
  Object.assign(new Tag(), { name: 'JavaScript' })
];
orm.saveWithRelations(article);

// 级联删除:删除文章和中间表关联(不删除标签本身)
orm.deleteWithRelations(article);

提示

  • 预加载查询会执行多次 SQL:主表、关联表、中间表
  • 关联数据为空数组 [] 时表示没有关联记录
  • 级联删除只会删除中间表的关联记录,不会删除关联的实体

注意

  • 级联更新会先删除所有旧的中间表记录,再插入新的关联
  • 大量关联数据可能影响查询性能
  • 建议合理使用预加载来优化性能