跳到主要内容

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
})
);
}

最佳实践

  1. 设置合理的 limit:根据 UI 需求设置,通常 20-100 条
  2. 避免大偏移量:offset 超过 1000 时考虑使用 findByCursor
  3. 使用索引字段:where 条件和 orderBy 尽量使用有索引的字段
  4. 结合 count:分页时同时获取总数,提供完整的分页信息
  5. 缓存查询结果:对于不常变化的数据,考虑缓存查询结果

参考