基础关联关系
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 | 目标实体类 | ✓ |
foreignKey | string | 外键字段名 | ✗(可自动推断) |
localKey | string | 本地键字段名,默认 'id' | ✗ |
cascade | CascadeType[] | 级联操作类型 | ✗ |
lazy | boolean | 是否延迟加载,默认 true | ✗ |
多对多特有配置
| 选项 | 类型 | 描述 | 必需 |
|---|---|---|---|
through | string | 中间表名 | ✓ |
throughForeignKey | string | 当前实体在中间表的外键 | ✗ |
throughOtherKey | string | 目标实体在中间表的外键 | ✗ |
级联操作类型
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);注意事项
- 外键约束:确保数据库表结构与关联配置一致
- 循环引用:使用
() => Class工厂函数避免循环引用问题 - 性能考虑:合理使用预加载,避免 N+1 查询问题
- 级联操作:谨慎使用级联删除,避免意外删除数据
- 外键命名:
@HasOne/@HasMany的 foreignKey 使用数据库列名(snake_case),@BelongsTo的 foreignKey 使用属性名(camelCase)