实现所需的Trait以及创建并插入测试数据

251852
2022/06/01 01:49:24

在使用 SeaORM 之前,需要进行一些初始操作。本章我们将创建数据表、导入初始示例数据以及实现所需的 trait。最后,我们通过查询所有分类列表来验证这些操作是否正常运行。

准备数据

请创建一个 PostgreSQL 数据库,并将以下SQL导入其中:

CREATE TABLE categoies ( -- 分类
	id SERIAL PRIMARY KEY, -- 自增主键
	name VARCHAR(20) NOT NULL UNIQUE, -- 分类名称
	is_del BOOLEAN NOT NULL DEFAULT FALSE -- 是否删除
);

CREATE TABLE articles ( -- 文章
	id SERIAL PRIMARY KEY, -- 自增主键
	category_id INT NOT NULL REFERENCES categoies(id), -- 文章所属分类的ID,外键
	title VARCHAR(255) NOT NULL, -- 文章标题
	content TEXT NOT NULL, -- 文章内容
	dateline TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 添加时间
	is_del BOOLEAN NOT NULL DEFAULT FALSE -- 是否删除
);

-- 插入示例数据
INSERT INTO categoies (id,name) VALUES
(1,'Rust'), (2,'Go'), (3,'Javascript');

编写实体并实现所需的 trait

// src/entity/category.rs
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "categoies")]
pub struct Model {
    #[sea_orm(primary_key)]
    #[serde(skip_deserializing)]
    pub id: i32,
    pub name: String,
    pub is_del: bool,
}

#[derive(Debug, Clone, Copy, EnumIter)]
pub enum Relation {}

impl RelationTrait for Relation {
    fn def(&self) -> sea_orm::RelationDef {
        panic!("没有定义关系")
    }

}

impl ActiveModelBehavior for ActiveModel {}

SeaORM 的实体和 Rust 的模块

SeaORM 将每一个数据表抽象成一个实体,而在 Rust 中,通常使用模块来表示实体。

通常,每个实体都会包含 EntityModelRelationActiveModel

Entity

官方文档

用于操作数据库。

Model

官方文档

用于定义数据表中的字段与Rust数据结构的映射关系。同时,在 SELECT 操作时,返回的也是 Model的实例。

Relation

官方文档

用于定义数据表之间的关系。目前,我们不作任何定义,而是直接抛出一个panic

ActiveModel

官方文档

也是数据表中字段与 Rust 数据结构的映射,不同于 Model的是,ActiveModel用于“写”操作,比如 UPDATEDELETE 等。

等等,这个 ActiveModel 是怎么来的?参考源码 可以发现,DeriveEntityModel 这个 derive 根据我们定义的Model,生成了包括 ActiveModel在内的多个数据结构

DeriveEntityModel

官方文档

这是一个【全能】的派生宏,它主要用于从指定的Model中,生成以下数据结构:

  • Entity
  • ActiveModel
  • Column
  • PrimaryKey

共享数据库连接

配置文件

WEB.ADDR=127.0.0.1:9527
DB.DSN="postgres://<用户名>:<密码>@pg.axum.rs:5432/axum_rs_seaorm"

共享状态结构体

给路由加上共享状态

// src/main.rs
dotenv().ok();
let cfg = config::Config::from_env().unwrap();
let conn = Database::connect(&cfg.db.dsn).await.unwrap();
let app = router::init().layer(Extension(Arc::new(state::AppState { conn })));

实现分类列表功能

从共享状态中获取数据库连接

// src/handler/mod.rs

fn get_conn<'a>(state: &'a AppState) -> &'a DatabaseConnection {
    &state.conn
}

从数据库中获取所有分类

// src/handler/category.rs
use std::sync::Arc;

use axum::Extension;
use sea_orm::{EntityTrait, QueryOrder};

use super::{get_conn, render, HtmlRespon};
use crate::{entity::category, state::AppState, view, AppError, Result};

pub async fn index(Extension(state): Extension<Arc<AppState>>) -> Result<HtmlRespon> {
    let handler_name = "category/index";
    let conn = get_conn(&state);
    let categies: Vec<category::Model> = category::Entity::find()
        .order_by_asc(category::Column::Id)
        .all(conn)
        .await
        .map_err(AppError::from)?;
    let tpl = view::CategoryTemplate { categies };
    render(tpl, handler_name)
}

我们重点来看这段代码:

let categies: Vec<category::Model> = category::Entity::find()
        .order_by_asc(category::Column::Id)
        .all(conn)
        .await
        .map_err(AppError::from)?;

category::Entity::find() -> Select<E>

文档

构造一个用于查询的Select<E>对象。

Select<E>

用于执行数据库SELECT操作的对象。

order_by_asc()

指定用于升序排序的字段。

category::Column::Id

指定字段名

all()

获取所有数据

将数据记录填充到视图中

// src/view.rs

#[derive(Template)]
#[template(path = "category.html")]
pub struct CategoryTemplate {
    pub categies: Vec<entity::category::Model>,
}

模板渲染

<!-- templates/category.html -->
<table class="table">
    <thead>
        <tr>
            <th>#</th>
            <th>名称</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        {% for category in categies %}
        <tr>
            <td>{{ category.id }}</td>
            <td>{{ category.name }}</td>
            <td>
                <a href="/category/edit/{{ category.id }}" class="btn btn-primary btn-sm">修改</a>
                <a href="/category/del/{{ category.id }}" class="btn btn-danger btn-sm" onclick="return confirm('确定删除“{{ category.name }}”?')">删除</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>

本章效果

category-list

本章代码位于01/实现trait及插入示例数据分支