Skip to content

Many To Many 关联

介绍

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

定义 Many To Many 关联

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

ts
import { Table, Field, FieldType, Model, ManyToMany } from '@ibestservices/ibest-orm';

@Table({ name: 'users' })
class User extends Model {
  @Field({ type: FieldType.TEXT, tag: ['notNull'] })
  name?: string;

  @Field({ type: FieldType.TEXT })
  email?: string;

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

  constructor(name: string, email: string) {
    super();
    this.name = name;
    this.email = email;
  }
}

@Table({ name: 'roles' })
class Role extends Model {
  @Field({ type: FieldType.TEXT, tag: ['notNull'] })
  name?: string;

  @Field({ type: FieldType.TEXT })
  description?: string;

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

  constructor(name: string, description?: string) {
    super();
    this.name = name;
    this.description = description;
  }
}

配置参数

target

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

through

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

throughForeignKey

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

throughOtherKey

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

中间表

多对多关联需要一个中间表来存储关联关系。IBest ORM 会自动根据关联配置创建中间表,无需手动创建:

typescript
// IBest ORM 会自动创建 user_roles 表,包含:
// - id (主键)
// - user_id (外键,关联到 users 表)
// - role_id (外键,关联到 roles 表)  
// - created_at (创建时间)
// - 唯一约束 (user_id, role_id)
// - 外键约束和级联删除

自动表创建

IBest ORM 会在首次使用多对多关联时自动创建中间表,包括:

  • 主键字段
  • 外键字段和约束
  • 唯一性约束
  • 创建时间字段
  • 级联删除规则

查询 Many To Many 关联

预加载关联数据

ts
// 查询用户及其角色
const userWithRoles = await this.orm.Session(User)
  .With('roles')
  .Where('id', 1)
  .FirstWithRelations();

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

反向查询

ts
// 查询角色及其用户
const roleWithUsers = await this.orm.Session(Role)
  .With('users')
  .Where('id', 1)
  .FirstWithRelations();

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

查询所有关联数据

ts
// 查询所有用户及其角色
const usersWithRoles = await this.orm.Session(User)
  .With('roles')
  .FindWithRelations();

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

注意事项

提示

  • IBest ORM 会自动创建和管理中间表
  • 中间表自动包含主键、外键约束和唯一性约束
  • 预加载查询会执行多次 SQL:主表、关联表、中间表
  • 关联数据为空数组 [] 时表示没有关联记录
  • 支持级联操作自动管理关联关系

注意

  • 中间表的外键字段会自动建立索引
  • 使用级联删除时会自动清理中间表的关联数据
  • 大量关联数据可能影响查询性能
  • 建议合理使用预加载和延迟加载来优化性能