本章我们将讨论如何使用 SeaORM 进行查询:查询条件、分页、查询单条记录等。
准备数据
为了更好地演示分页效果,我们通过 SQL 语句添加一些示例数据:
接收参数
为了让我们的分类列表能接收到各种参数,我们需要进行如下操作:
定义参数结构体
// src/param.rs
#[derive(Debug, Clone, Deserialize)]
pub struct CategoryParams {
pub keyword: Option<String>, // 用于搜索的关键字
pub is_del: Option<i32>, // 是否删除
pub sort: Option<String>, // 排序
pub page_size: Option<usize>, // 每页记录条数
pub page: Option<usize>, // 当前页码
}
// ...
在handler中接收参数
// src/handler/category.rs
pub async fn index(
Extension(state): Extension<Arc<AppState>>,
Query(params): Query<param::CategoryParams>,
) -> Result<HtmlRespon> {
// ...
}
模板
根据条件过滤数据 - filter()
SeaORM 提供多过滤数据的方式,对于多条记录而言,filter()
是最常用的方法。
简单查询
比如,要查询所有未删除的分类:
category::Entity::find()
.filter(category::Column::IsDel.eq(false))
// SELECT * FROM categories WHERE is_del=false
模糊查询
比如,要查询名字中带有a
的分类:
category::Entity::find()
.filter(category::Column::Name.contains("a"))
// SELECT * FROM categories WHERE name LIKE '%a%'
组合过滤
category::Entity::find()
.filter(
Condition.all()
.add(category::Column::IsDel.eq(false))
.add(category::Column::Name.contains("a"))
)
// SELECT * FROM categories WHERE is_del=false AND name LIKE '%a%'
可选过滤
很多时候我们需要的是可选过滤:即只有用户指定了某些条件才过滤,否则显示所有记录。在 axum 中,我们可以通过 Option
来获取可选参数。而 SeaORM 也对这些 Option
类型的参数提供了过滤方式——add_option()
。
let name: Option<String> = Some(String::from("a"));
category::Entity::find()
.filter(
Condition.all()
.add_option(
name.map(|n| category::Column::Name.contains(&n))
)
)
// -- 当 name 是 None 时:
// SELECT * FROM categories
// -- 当 name 是 Some("a") 时:
// SELECT * FROM categories WHERE is_del=false AND name LIKE '%a%'
组合可选过滤
同样的,我们也可以组合多个可选过滤
let name: Option<String> = Some(String::from("a"));
let is_del:Option<bool> = Some(false);
category::Entity::find()
.filter(
Condition.all()
.add_option(
name.map(|n| category::Column::Name.contains(&n))
)
.add_option(
is_del.map(|n| category::Column::IsDel.eq(n))
)
)
设置排序 - order_by()
可以使用 order_by(字段,排序方式)
来对数据进行排序。其中的 字段
通常是一个Column
,比如category::Column::Id
,而 排序方式
是 Order
枚举:
Order::Asc
:升序Order::Desc
:降序
比如,要让所有记录按id
的降序排列:
category::Entity::find()
.order_by(category::Column::Id, Order::Desc)
// SELECT * FROM categoies ORDER BY id DESC
由于ASC
和DESC
太常见了,所以 SeaORM 直接提供了两个方法来简化操作:
order_by_asc(字段)
:即order_by(字段, Order::Asc)
order_by_desc(字段)
:即order_by(字段, Order:Desc)
分页 - paginate()
SeaORM 很贴心的提供了 paginate(conn,page_size)->Paginator
方法,方便地进行分页。
它的conn
参数是实现了ConnectionTrait
的对象,我们的项目中使用的是 DatabaseConnection(src/state.rs),它实现了这个ConnectionTrait
。而 page_size
是次获取记录的条数。
它的返回值是一个Paginator对象,它提供了诸多有用的方法:
async num_pages()
:获取分页的总页数async num_items()
:获取总的记录数async fetch_page(page)
:获取第page
页的记录,page
是从0
开始算的。
简单分页
有了这些知识的铺垫,现在来看一下如何实现一个简单的分页:
let page:usize = 0;
let page_size = 15usize;
let paginator = category::Entity::find().paginate(conn, page_size);
let page_total = paginator.num_pages().await.unwrap();
let categies: Vec<category::Model> = paginator
.fetch_page(page)
.await
.unwrap();
复杂分页
配合前面讨论的组合过滤、排序等,可以实现复杂的分页:
pub async fn index(
Extension(state): Extension<Arc<AppState>>,
Query(params): Query<param::CategoryParams>,
) -> Result<HtmlRespon> {
let handler_name = "category/index";
let page = params.page(); // 当前页码
let page_size = params.page_size(); // 每页条数,默认15
let conn = get_conn(&state);
let mut selc = category::Entity::find().filter(
Condition::all()
.add_option(
params
.keyword_opt()
.map(|n| category::Column::Name.contains(&n)),
)
.add_option(params.is_del_opt().map(|n| category::Column::IsDel.eq(n))),
);
if let Some(ord) = params.order() {
selc = selc.order_by(category::Column::Id, ord);
};
let paginator = selc.paginate(conn, page_size);
let page_total = paginator.num_pages().await.map_err(AppError::from)?;
let categies: Vec<category::Model> = paginator
.fetch_page(page)
.await
.map_err(AppError::from)
.map_err(log_error(handler_name))?;
let tpl = view::CategoryTemplate {
categies,
params,
page_total,
};
render(tpl, handler_name)
}
模板中显示分页链接
<!-- templates/category.html -->
<nav>
<ul class="pagination pagination">
{% for page in 0..page_total %}
{% if page == params.page() %}
<li class="page-item active">
<span class="page-link">{{ page + 1}}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{page}}&keyword={{params.keyword()}}&is_del={{params.is_del()}}&sort={{params.sort()}}&page_size={{params.page_size()}}">{{ page + 1}}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
查询单条数据
当需要查询单条数据时,我们要做的是将all()
改为one()
,它返回的是一个Option
:
let cate: Option<category::Model> = category::Entity::find()
.filter(category::Column::Id.eq(1))
.one()
.await
.unwrap();
对于通过主键来查询,我们可以使用更简单的方法 find_by_id()
pub async fn find(
Extension(state): Extension<Arc<AppState>>,
Path(id): Path<i32>,
) -> Result<String> {
let handler_name = "category/find";
let conn = get_conn(&state);
let cate: Option<category::Model> = category::Entity::find_by_id(id)
.one(conn)
.await
.map_err(AppError::from)
.map_err(log_error(handler_name))?;
match cate {
Some(cate) => Ok(format!("id: {}, 名称: {}", cate.id, cate.name)),
None => Err(AppError::notfound()),
}
}
本章代码位于02/查询数据分支。