find
查询多个实体,支持分页、排序和复杂查询条件。
方法签名
find(options: FindOptions<T>): Observable<InstanceType<T>[]>
参数
interface FindOptions<T> {
where: RuleGroup<InstanceType<T>>;
orderBy?: OrderBy[];
limit?: number; // 默认 100
offset?: number; // 默认 0
}
示例
基础查询
import { firstValueFrom } from 'rxjs';
const todos = await firstValueFrom(
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 20
})
);
排序
const sorted = await firstValueFrom(
Todo.find({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' },
{ field: 'id', sort: 'asc' }
]
})
);
过滤
const completed = await firstValueFrom(
Todo.find({
where: {
combinator: 'and',
rules: [{ field: 'completed', operator: '=', value: true }]
},
orderBy: [{ field: 'completedAt', sort: 'desc' }]
})
);
复杂条件
const urgent = await firstValueFrom(
Todo.find({
where: {
combinator: 'and',
rules: [
{ field: 'completed', operator: '=', value: false },
{
combinator: 'or',
rules: [
{ field: 'priority', operator: '=', value: 'high' },
{ field: 'dueDate', operator: '<=', value: new Date(Date.now() + 86400000) }
]
}
]
},
orderBy: [{ field: 'dueDate', sort: 'asc' }]
})
);
分页
function getTodos(page: number, pageSize: number) {
return firstValueFrom(
Todo.find({
where: { combinator: 'and', rules: [] },
orderBy: [{ field: 'createdAt', sort: 'desc' }],
limit: pageSize,
offset: (page - 1) * pageSize
})
);
}
// 获取第一页,每页 20 条
const page1 = await getTodosByPage({ page: 1, pageSize: 20 });
// 获取第二页
const page2 = await getTodosByPage({ page: 2, pageSize: 20 });
6. 在组件中使用
Angular
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
@Component({
selector: 'app-todo-list',
template: `
<div *ngFor="let todo of todos$ | async">
<h3>{{ todo.title }}</h3>
<p>{{ todo.description }}</p>
</div>
`
})
export class TodoListComponent implements OnInit {
todos$!: Observable<Todo[]>;
ngOnInit() {
this.todos$ = Todo.find({
where: {
combinator: 'and',
rules: [{ field: 'completed', operator: '=', value: false }]
},
orderBy: [
{ field: 'priority', sort: 'desc' },
{ field: 'createdAt', sort: 'asc' }
],
limit: 50
});
}
}
React
import { useEffect, useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const [page, setPage] = useState(1);
const pageSize = 20;
useEffect(() => {
const subscription = Todo.find({
where: {
combinator: 'and',
rules: []
},
orderBy: [{ field: 'createdAt', sort: 'desc' }],
limit: pageSize,
offset: (page - 1) * pageSize
}).subscribe(setTodos);
return () => subscription.unsubscribe();
}, [page]);
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
<h3>{todo.title}</h3>
<p>{todo.description}</p>
</div>
))}
<button onClick={() => setPage(p => Math.max(1, p - 1))}>上一页</button>
<button onClick={() => setPage(p => p + 1)}>下一页</button>
</div>
);
}
Vue
<template>
<div>
<div v-for="todo in todos" :key="todo.id">
<h3>{{ todo.title }}</h3>
<p>{{ todo.description }}</p>
</div>
<button @click="prevPage">上一页</button>
<button @click="nextPage">下一页</button>
</div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
const todos = ref<Todo[]>([]);
const page = ref(1);
const pageSize = 20;
watchEffect(() => {
const subscription = Todo.find({
where: {
combinator: 'and',
rules: []
},
orderBy: [{ field: 'createdAt', sort: 'desc' }],
limit: pageSize,
offset: (page.value - 1) * pageSize
}).subscribe(value => {
todos.value = value;
});
return () => subscription.unsubscribe();
});
function prevPage() {
if (page.value > 1) page.value--;
}
function nextPage() {
page.value++;
}
</script>
7. 搜索功能实现
async function searchTodos(keyword: string) {
return firstValueFrom(
Todo.find({
where: {
combinator: 'or',
rules: [
{
field: 'title',
operator: 'contains',
value: keyword
},
{
field: 'description',
operator: 'contains',
value: keyword
}
]
},
orderBy: [{ field: 'createdAt', sort: 'desc' }],
limit: 100
})
);
}
// 搜索包含 "重要" 的待办事项
const results = await searchTodos('重要');
find vs findAll vs findByCursor
| 方法 | 分页方式 | 性能 | 适用场景 |
|---|---|---|---|
find | 基于偏移量 | 中等 | 传统分页,需要跳页 |
findAll | 无分页 | 可能较慢 | 小数据集,需要全部数据 |
findByCursor | 基于游标 | 高 | 无限滚动,大数据集 |
// find - 传统分页
const page1 = await firstValueFrom(
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 20,
offset: 0
})
);
// findAll - 获取所有数据
const allTodos = await firstValueFrom(
Todo.findAll({
where: { combinator: 'and', rules: [] }
})
);
// findByCursor - 游标分页
const cursorPage = await firstValueFrom(
Todo.findByCursor({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' },
{ field: 'id', sort: 'asc' }
],
limit: 20
})
);
性能优化
1. 合理设置 limit
// ✅ 好:根据需求设置合理的 limit
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 50 // 根据 UI 显示需求设置
});
// ❌ 不好:limit 过大导致性能问题
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 10000 // 过大
});
2. 使用索引字段排序
// ✅ 好:使用索引字段排序
Todo.find({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' }, // createdAt 有索引
{ field: 'id', sort: 'asc' } // id 是主键
],
limit: 20
});
// ⚠️ 慢:使用非索引字段排序
Todo.find({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'description', sort: 'asc' } // 非索引字段
],
limit: 20
});
3. 避免大偏移量
// ⚠️ 性能问题:大偏移量
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 20,
offset: 10000 // 偏移量过大,性能差
});
// ✅ 更好的方案:使用 findByCursor
Todo.findByCursor({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' },
{ field: 'id', sort: 'asc' }
],
after: lastItem,
limit: 20
});
常见模式
1. 带总数的分页
async function getTodosWithTotal(page: number, pageSize: number) {
const where = {
combinator: 'and' as const,
rules: []
};
const [items, total] = await Promise.all([
firstValueFrom(
Todo.find({
where,
orderBy: [{ field: 'createdAt', sort: 'desc' as const }],
limit: pageSize,
offset: (page - 1) * pageSize
})
),
firstValueFrom(Todo.count({ where }))
]);
return {
items,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
}
2. 过滤 + 搜索 + 分页
interface TodoQueryParams {
keyword?: string;
completed?: boolean;
priority?: string;
page: number;
pageSize: number;
}
async function queryTodos(params: TodoQueryParams) {
const rules: any[] = [];
// 搜索关键词
if (params.keyword) {
rules.push({
combinator: 'or',
rules: [
{ field: 'title', operator: 'contains', value: params.keyword },
{ field: 'description', operator: 'contains', value: params.keyword }
]
});
}
// 完成状态
if (params.completed !== undefined) {
rules.push({
field: 'completed',
operator: '=',
value: params.completed
});
}
// 优先级
if (params.priority) {
rules.push({
field: 'priority',
operator: '=',
value: params.priority
});
}
return firstValueFrom(
Todo.find({
where: {
combinator: 'and',
rules
},
orderBy: [{ field: 'createdAt', sort: 'desc' }],
limit: params.pageSize,
offset: (params.page - 1) * params.pageSize
})
);
}
最佳实践
- 设置合理的 limit:根据 UI 需求设置,通常 20-100 条
- 避免大偏移量:offset 超过 1000 时考虑使用 findByCursor
- 使用索引字段:where 条件和 orderBy 尽量使用有索引的字段
- 结合 count:分页时同时获取总数,提供完整的分页信息
- 缓存查询结果:对于不常变化的数据,考虑缓存查询结果
参考
- findAll - 查询所有数据
- findByCursor - 基于游标的分页查询
- findOne - 查询单个实体
- count - 统计数量
- 查询操作符 - 查询条件操作符详解