Skip to content

基础关联关系

IBest ORM 提供了完整的关联关系支持,包括一对一、一对多、多对一和多对多关联。通过装饰器注解,您可以轻松定义实体之间的关联关系。

关联类型概览

关联类型装饰器描述示例
一对一@HasOne一个实体拥有另一个实体用户 → 用户资料
一对多@HasMany一个实体拥有多个子实体用户 → 订单列表
多对一@BelongsTo多个实体属于一个父实体订单 → 用户
多对多@ManyToMany多个实体关联多个实体用户 ↔ 角色

一对一关联 (@HasOne)

一对一关联表示一个实体拥有另一个实体的单个实例。

typescript
import { Table, Column, PrimaryKey, HasOne, BelongsTo, ColumnType } from '@ibestservices/ibest-orm';

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

  @Column()
  name!: string;

  @Column()
  email?: string;

  // 一对一关联:用户拥有一个用户资料
  @HasOne(() => UserProfile, { foreignKey: 'user_id' })
  profile?: UserProfile;
}

@Table({ name: 'user_profiles' })
class UserProfile {
  @PrimaryKey()
  id?: number;

  @Column({ name: 'user_id', type: ColumnType.INTEGER })
  userId?: number;

  @Column()
  bio?: string;

  @Column()
  avatar?: string;

  // 反向关联:用户资料属于一个用户
  @BelongsTo(() => User, { foreignKey: 'userId' })
  user?: User;
}

一对多关联 (@HasMany)

一对多关联表示一个实体拥有多个子实体。

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

  @Column()
  name!: string;

  // 一对多关联:用户拥有多个订单
  @HasMany(() => Order, { foreignKey: 'user_id' })
  orders?: Order[];
}

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

  @Column({ name: 'user_id', type: ColumnType.INTEGER })
  userId?: number;

  @Column()
  productName!: string;

  @Column({ type: ColumnType.REAL })
  amount?: number;

  // 反向关联:订单属于一个用户
  @BelongsTo(() => User, { foreignKey: 'userId' })
  user?: User;

  // 嵌套一对多关联:订单拥有多个订单项
  @HasMany(() => OrderItem, { foreignKey: 'order_id' })
  items?: OrderItem[];
}

多对多关联 (@ManyToMany)

多对多关联通过中间表连接两个实体,每个实体可以关联多个另一个实体的实例。

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

  @Column()
  name!: 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目标实体类
foreignKeystring外键字段名✗(可自动推断)
localKeystring本地键字段名,默认 'id'
cascadeCascadeType[]级联操作类型
lazyboolean是否延迟加载,默认 true

多对多特有配置

选项类型描述必需
throughstring中间表名
throughForeignKeystring当前实体在中间表的外键
throughOtherKeystring目标实体在中间表的外键

级联操作类型

typescript
import { CascadeType } from '@ibestservices/ibest-orm';

// 可用的级联类型
CascadeType.Create  // 级联创建
CascadeType.Update  // 级联更新
CascadeType.Delete  // 级联删除
CascadeType.All     // 所有级联操作

使用示例

创建带关联的实体

方式一:使用级联创建(推荐)

typescript
import { getORM } from '@ibestservices/ibest-orm';

const orm = getORM();
orm.migrate(User, UserProfile, Order);

// 创建用户及其关联数据
const user = new User();
user.name = 'John Doe';
user.email = 'john@example.com';
user.profile = Object.assign(new UserProfile(), {
  bio: 'Software Developer',
  avatar: 'avatar.jpg'
});
user.orders = [
  Object.assign(new Order(), { productName: 'Laptop', amount: 1299.99 })
];

// 级联插入:自动创建用户、资料和订单
orm.insertWithRelations(user);

方式二:手动创建

typescript
// 创建用户
const user = new User();
user.name = 'John Doe';
user.email = 'john@example.com';
orm.insert(user);

// 创建关联的用户资料
const profile = new UserProfile();
profile.userId = user.id;
profile.bio = 'Software Developer';
profile.avatar = 'avatar.jpg';
orm.insert(profile);

// 创建关联的订单
const order = new Order();
order.userId = user.id;
order.productName = 'Laptop';
order.amount = 1299.99;
orm.insert(order);

查询带关联的实体

typescript
// 预加载关联数据
const users = orm.query(User)
  .with('profile')
  .with('orders')
  .find();

console.log(users[0]?.profile?.bio);
console.log(users[0]?.orders?.length);

// 查询单个用户及其关联
const user = orm.query(User)
  .with('profile')
  .where({ email: 'john@example.com' })
  .first();

// 反向查询:从订单查用户
const orders = orm.query(Order)
  .with('user')
  .find();

console.log(orders[0]?.user?.name);

注意事项

  1. 外键约束:确保数据库表结构与关联配置一致
  2. 循环引用:使用 () => Class 工厂函数避免循环引用问题
  3. 性能考虑:合理使用预加载,避免 N+1 查询问题
  4. 级联操作:谨慎使用级联删除,避免意外删除数据
  5. 外键命名@HasOne/@HasMany 的 foreignKey 使用数据库列名(snake_case),@BelongsTo 的 foreignKey 使用属性名(camelCase)