延迟加载 (Lazy Loading)
IBest ORM 提供两种关联数据加载策略:默认的显式加载和真正的延迟加载。
加载策略对比
| 策略 | 方法 | 行为 | 适用场景 |
|---|---|---|---|
| 显式加载(默认) | with() | 关联属性为 undefined,需手动指定加载 | 大多数场景 |
| 真正延迟加载 | lazy() | 访问关联属性时自动查询数据库 | 不确定是否需要关联数据 |
默认行为:显式加载
在 IBest ORM 中,关联数据默认不会自动加载。您需要使用 with() 方法显式指定要加载的关联。
typescript
// 不加载关联数据
const user = orm.query(User)
.where({ id: 1 })
.first();
console.log(user?.orders); // undefined - 关联未加载
// 使用 with() 显式加载关联数据
const userWithOrders = orm.query(User)
.with('orders')
.where({ id: 1 })
.first();
console.log(userWithOrders?.orders); // Order[] - 关联已加载真正延迟加载:lazy() 方法
使用 lazy() 方法可以启用真正的延迟加载。启用后,当您访问关联属性时,ORM 会自动执行数据库查询获取关联数据。
typescript
// 启用延迟加载
const user = orm.query(User)
.lazy()
.where({ id: 1 })
.first();
// 访问 orders 属性时自动查询数据库
console.log(user?.orders); // Order[] - 自动加载
console.log(user?.profile); // Profile - 自动加载lazy() 的工作原理
lazy() 方法会为实体的关联属性创建 getter 代理:
- 首次访问关联属性时,自动执行 SQL 查询
- 查询结果会被缓存,后续访问不会重复查询
- 支持 HasOne、HasMany、BelongsTo 关联(ManyToMany 暂不支持)
typescript
const user = orm.query(User).lazy().where({ id: 1 }).first();
// 第一次访问:执行 SQL 查询
const orders1 = user?.orders; // SELECT * FROM order WHERE user_id = ?
// 第二次访问:使用缓存,不再查询
const orders2 = user?.orders; // 直接返回缓存结果使用场景
typescript
// 场景1:不确定是否需要关联数据
function getUserInfo(userId: number, includeOrders: boolean) {
const user = orm.query(User).lazy().where({ id: userId }).first();
const result: Record<string, unknown> = { name: user?.name };
// 只有需要时才会触发查询
if (includeOrders) {
result.orders = user?.orders; // 此时才查询订单
}
return result;
}
// 场景2:条件性访问关联
const user = orm.query(User).lazy().where({ id: 1 }).first();
if (user?.name === 'VIP') {
// 只有 VIP 用户才加载订单
console.log(user.orders);
}注意
lazy() 可能导致 N+1 查询问题。在批量查询场景中,建议使用 with() 预加载。
关联定义
HasOne 关联
一对一关联,返回单个对象或 undefined。
typescript
import { Table, Column, PrimaryKey, HasOne } from '@ibestservices/ibest-orm';
@Table()
class User {
@PrimaryKey()
id?: number;
@Column()
name!: string;
@HasOne(() => UserProfile, { foreignKey: 'user_id' })
profile?: UserProfile;
}
// 使用
const user = orm.query(User)
.with('profile')
.where({ id: 1 })
.first();
console.log(user?.profile?.bio);HasMany 关联
一对多关联,返回数组。
typescript
@Table()
class User {
@PrimaryKey()
id?: number;
@Column()
name!: string;
@HasMany(() => Order, { foreignKey: 'user_id' })
orders?: Order[];
}
// 使用
const user = orm.query(User)
.with('orders')
.where({ id: 1 })
.first();
console.log(user?.orders?.length);BelongsTo 关联
多对一关联,返回单个对象或 undefined。
typescript
@Table()
class Order {
@PrimaryKey()
id?: number;
@Column({ name: 'user_id', type: ColumnType.INTEGER })
userId?: number;
@Column()
productName!: string;
@BelongsTo(() => User, { foreignKey: 'userId' })
user?: User;
}
// 使用
const order = orm.query(Order)
.with('user')
.where({ id: 1 })
.first();
console.log(order?.user?.name);ManyToMany 关联
多对多关联,返回数组。
typescript
@Table()
class User {
@PrimaryKey()
id?: number;
@Column()
name!: string;
@ManyToMany(() => Role, {
through: 'user_roles',
throughForeignKey: 'user_id',
throughOtherKey: 'role_id'
})
roles?: Role[];
}
// 使用
const user = orm.query(User)
.with('roles')
.where({ id: 1 })
.first();
console.log(user?.roles?.map(r => r.name));按需加载示例
typescript
function getUserBasicInfo(userId: number) {
// 只查询用户基本信息,不加载关联
return orm.query(User)
.where({ id: userId })
.first();
}
function getUserWithOrders(userId: number) {
// 需要订单时才加载
return orm.query(User)
.with('orders')
.where({ id: userId })
.first();
}
function getUserFullProfile(userId: number) {
// 需要完整信息时加载所有关联
return orm.query(User)
.with('profile')
.with('orders')
.with('roles')
.where({ id: userId })
.first();
}延迟加载 vs 预加载
延迟加载的优势
- 减少初始查询复杂度:只查询主实体数据
- 按需加载:只加载实际使用的关联数据
- 内存效率:避免加载不必要的数据
typescript
// 延迟加载:只查询需要的数据
const user = orm.query(User)
.where({ id: 1 })
.first();
// 后续需要时再查询关联
const userWithOrders = orm.query(User)
.with('orders')
.where({ id: 1 })
.first();预加载的优势
- 避免 N+1 查询:一次性加载所有数据
- 性能可预测:查询次数固定
- 适合批量处理:处理大量数据时更高效
typescript
// 预加载:一次性加载所有数据
const users = orm.query(User)
.with('profile')
.find();
// 访问关联数据不会触发额外查询
users.forEach(user => {
console.log(user.profile?.bio);
});性能对比
| 场景 | 延迟加载 | 预加载 |
|---|---|---|
| 单条记录查询 | 适合,按需加载 | 适合,一次加载 |
| 批量记录查询 | 可能产生 N+1 问题 | 推荐,避免 N+1 |
| 不确定是否需要关联 | 推荐,节省资源 | 可能浪费资源 |
| 确定需要关联数据 | 需要额外查询 | 推荐,一次完成 |
最佳实践
- 明确需求:根据业务需求决定是否加载关联
- 避免 N+1:批量查询时使用
with()预加载 - 按需加载:单条查询时可以按需加载关联
- 合理设计:关联层级不宜过深