Rust Web 全栈开发(六):在 Web 项目中使用 MySQL 数据库

Rust 与 Mysql 交互篇之 Sqlx 数据库在编程中是一个很重要的环节,主要体现在数据的存储上,方方面面都会用到,重要性不言而喻。本教程以`sqlx`及`diesel`两个crate做为基础的教程,介绍其基本用法 阅读详情

Rust Web 全栈开发(六):在 Web 项目中使用 MySQL 数据库

参考视频:https://www.bilibili.com/video/BV1RP4y1G7KF

继续使用之前的 Actix 项目。

配置项目

打开 Cargo.toml,把 edition 改成 “2021”。

修改 [dependencies] 部分:

actix-web="4.1.0"
actix-rt="2.7.0"
dotenv = "0.15.0"
chrono = {version = "0.4.19", features = ["serde"]}
serde = {version = "1.0.140", features = ["derive"]}
sqlx = {version = "0.6.0", default_features = false, features = [
    "mysql",
    "runtime-tokio-rustls",
    "macros",
    "chrono",
]}

注意:在添加 crate 时,注意使用版本要相互兼容,否则会出现编译警告。具体需要访问 crates.io 来查看合适的版本。

在终端执行命令 cargo build,构建成功:

在这里插入图片描述

修改 AppState

把 state.rs 修改为:

use std::sync::Mutex;
// use super::models::Course;
use sqlx::MySqlPool;

pub struct AppState {
    pub health_check_response: String,
    pub visit_count: Mutex<u32>,
    // pub courses: Mutex<Vec<Course>>,
    pub db: MySqlPool,
}

现在,课程不再存储在内存中,而是存储在 MySQL 数据库中。

修改 Course

打开 models.rs,让 Course 结构体实现 sqlx::FromRow trait,便于读取数据时的数据转换。

#[derive(Deserialize, Serialize, sqlx::FromRow, Debug, Clone)]
pub struct Course {
    pub teacher_id: i32,
    pub id: Option<i32>,
    pub name: String,
    pub time: Option<NaiveDateTime>,
}

数据库准备

在这里插入图片描述

新建一个名为 course 的 MySQL 数据库,再新建一个名为 course 的表:

在这里插入图片描述

time 如果用 timestamp 类型的话,会报错:error[E0277]: the trait bound `NaiveDate: From<DateTime>` is not satisfied,原因是:the trait `From<DateTime>` is not implemented for `NaiveDate`。

time 如果用 date 类型的话,会报错:mismatched types,原因是:Rust type `core::option::Option<chrono::naive::datetime::NaiveDateTime>` (as SQL type `DATETIME`) is not compatible with SQL type `DATE`。

内容如下:

在这里插入图片描述

连接请求

在 webservice 目录下,新建名为 .env 的文件,在文件内写入请求 URL,形如:

DATABASE_URL=mysql://{user}:{password}@{IP}:{port}/{database name}

这里,我的请求 URL 是:

DATABASE_URL=mysql://root:12138@127.0.0.1:3306/course

读取 MySQL 数据库

在 webservice/src 目录下新建 db_access.rs,实现访问 MySQL 数据库,执行 SQL 语句的功能。

use super::models::*;
use sqlx::MySqlPool;

pub async fn get_courses_for_teacher_db(pool: &MySqlPool, teacher_id: i32) -> Vec<Course> {
    let rows: Vec<Course> = sqlx::query_as(
        "SELECT id, teacher_id, name, time
            FROM course
            WHERE teacher_id = ?"
    )
        .bind(teacher_id)
        .fetch_all(pool) // 获取所有记录
        .await
        .unwrap();

    rows.iter()
        .map(|r| Course {
            id: Some(r.id.expect("Unknown")),
            teacher_id: r.teacher_id,
            name: r.name.clone(),
            time: Some(chrono::NaiveDateTime::from(r.time.unwrap())),
        })
        .collect()
}

pub async fn get_course_details_db(pool: &MySqlPool, teacher_id: i32, course_id: i32) -> Course {
    let row: Course = sqlx::query_as(
        "SELECT id, teacher_id, name, time
            FROM course
            WHERE teacher_id = ? and id = ?"

    )
        .bind(teacher_id)
        .bind(course_id)
        .fetch_one(pool) // 获取单条记录
        .await
        .unwrap();

    Course {
        id: Some(row.id.expect("Unknown")),
        teacher_id: row.teacher_id,
        name: row.name.clone(),
        time: Some(chrono::NaiveDateTime::from(row.time.unwrap())),
    }
}

pub async fn post_new_course_db(pool: &MySqlPool, new_course: Course) -> Result<(), sqlx::Error> {
    let _insert_query = sqlx::query!(
        "INSERT INTO course (id, teacher_id, name, time)
            VALUES (?, ?, ?, ?)",
        new_course.id.unwrap(),
        new_course.teacher_id,
        new_course.name,
        new_course.time
    )
        .execute(pool)
        .await?;

    Ok(())
}

其他修改

修改 teacher_service.rs,把 db_access.rs 的路径添加进去。

把读取 MySQL 数据库的内容添加进去,再对 AppState 实例的构建的代码进行修改。

use actix_web::{web, App, HttpServer};
use std::sync::Mutex;
use dotenv::dotenv;
use sqlx::mysql::MySqlPoolOptions;
use std::env;
use std::io;

#[path = "../handlers.rs"]
mod handlers;
#[path = "../models.rs"]
mod models;
#[path = "../routers.rs"]
mod routers;
#[path = "../state.rs"]
mod state;
#[path = "../db_access.rs"]
mod db_access;

use routers::*;
use state::AppState;

#[actix_rt::main]
async fn main() -> io::Result<()> {
    // 检测并读取 .env 文件中的内容,若不存在也会跳过异常
    dotenv().ok();

    let db_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL 没有在 .env 文件里设置");
    
    // 创建数据库连接池
    let db_pool = MySqlPoolOptions::new()
        .connect(&db_url)
        .await
        .unwrap();
    
    let shared_data = web::Data::new(AppState {
        health_check_response: "I'm OK.".to_string(),
        visit_count: Mutex::new(0),
        // courses: Mutex::new(vec![]),
        db: db_pool,
    });
    
    let app = move || {
        App::new()
            .app_data(shared_data.clone())
            .configure(general_routes)
            .configure(course_routes)
    };

    HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}

修改 handlers.rs,对数据库的操作都调用 db_access.rs 中的函数

use super::state::AppState;
use actix_web::{web, HttpResponse};
use super::models::Course;
use super::db_access::*;

pub async fn health_check_handler(app_state: web::Data<AppState>) -> HttpResponse {
    println!("incoming for health check");

    let health_check_response = &app_state.health_check_response;
    let mut visit_count = app_state.visit_count.lock().unwrap();
    let response = format!("{} {} times", health_check_response, visit_count);
    *visit_count += 1;

    HttpResponse::Ok().json(&response)
}

pub async fn new_course(
    new_course: web::Json<Course>,
    app_state: web::Data<AppState>,
) -> HttpResponse {
    let _result = post_new_course_db(&app_state.db, new_course.into()).await;

    HttpResponse::Ok().json("Course added")
}

pub async fn get_courses_for_teacher(
    app_state: web::Data<AppState>,
    params: web::Path<i32>,
) -> HttpResponse {
    let teacher_id = params.into_inner();
    let courses = get_courses_for_teacher_db(&app_state.db, teacher_id).await;

    HttpResponse::Ok().json(courses)
}

pub async fn get_course_detail(
    app_state: web::Data<AppState>,
    params: web::Path<(i32, i32)>,
) -> HttpResponse {
    let (teacher_id, course_id) = params.into_inner();
    let course = get_course_details_db(&app_state.db, teacher_id, course_id).await;

    HttpResponse::Ok().json(course)
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::http::StatusCode;
    use std::sync::Mutex;
    use dotenv::dotenv;
    use sqlx::mysql::MySqlPoolOptions;
    use std::env;
    use chrono::{NaiveDate, NaiveDateTime, NaiveTime};

    #[actix_rt::test]
    async fn post_course_test() {
        // 检测并读取 .env 文件中的内容,若不存在也会跳过异常
        dotenv().ok();

        let db_url = env::var("DATABASE_URL")
            .expect("DATABASE_URL 没有在 .env 文件里设置");

        // 创建数据库连接池
        let db_pool = MySqlPoolOptions::new()
            .connect(&db_url)
            .await
            .unwrap();

        let course = web::Json(Course {
            teacher_id: 1,
            name: "Test course".into(),
            id: Some(3),
            time: Some(NaiveDateTime::new(
                NaiveDate::from_ymd_opt(2025, 7, 12).expect("Unknown date"),
                NaiveTime::from_hms_opt(10, 15, 0).expect("Unknown time"),
            )),
        });

        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            db: db_pool,
        });

        // 模拟添加课程的请求
        let response = new_course(course, app_state).await;

        assert_eq!(response.status(), StatusCode::OK);
    }

    #[actix_rt::test]
    async fn get_all_courses_success() {
        // 检测并读取 .env 文件中的内容,若不存在也会跳过异常
        dotenv().ok();

        let db_url = env::var("DATABASE_URL")
            .expect("DATABASE_URL 没有在 .env 文件里设置");

        // 创建数据库连接池
        let db_pool = MySqlPoolOptions::new()
            .connect(&db_url)
            .await
            .unwrap();

        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            db: db_pool,
        });

        let teacher_id: web::Path<i32> = web::Path::from(1);
        let response = get_courses_for_teacher(app_state, teacher_id).await;

        assert_eq!(response.status(), StatusCode::OK);
    }

    #[actix_rt::test]
    async fn get_one_course_success() {
        // 检测并读取 .env 文件中的内容,若不存在也会跳过异常
        dotenv().ok();

        let db_url = env::var("DATABASE_URL")
            .expect("DATABASE_URL 没有在 .env 文件里设置");

        // 创建数据库连接池
        let db_pool = MySqlPoolOptions::new()
            .connect(&db_url)
            .await
            .unwrap();

        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            db: db_pool,
        });

        let params: web::Path<(i32, i32)> = web::Path::from((1, 1));
        let response = get_course_detail(app_state, params).await;

        assert_eq!(response.status(), StatusCode::OK);
    }
}

测试

在终端执行命令 cargo test --bin teacher_service,三个测试都通过了:

在这里插入图片描述

成功向 MySQL 数据库插入了一条数据:

在这里插入图片描述

cd 到 webservice,执行命令 cargo run,在浏览器中测试两个查询功能,都成功了:

在这里插入图片描述

在这里插入图片描述

尾声

视频教程 中使用的是 PostgreSQL 数据库,本文使用的是 MySQL 数据库,在代码方面存在很多细微的差异(集中体现在 db_access.rs 中 SQL 语句的写法),请读者仔细比对。

使用 Docker 搭建 Rust Web 应用开发环境——AI教你学Docker 本文详细介绍了如何使用Docker和Docker Compose搭建Rust Web应用(如actix-web/axum/rocket)开发环境,并集成MySQL、Redis、Kafka等常用中间件。内容包括:项目目录结构建议、Rust应用Dockerfile配置示例、docker-compose.yml完整配置、中间件连接代码示例以及开发运行流程。特别强调了开发注意事项,如端口冲突、服务就绪检查、热重载实现、数据持久化和配置安全等。该方案适合个人开发、本地测试和小型团队协作,提供了从环境搭建到实际开发的全 阅读详情

相关推荐

使用 Actix-Web、SQLx 和 Redis 构建高性能 Rust Web 服务

Actix-Web是一个用于构建 Web 应用程序的高性能、异步框架,基于 Rust 语言。异步:使用tokio和async-std等异步运行时,提供高性能和高并发处理能力。中间件:支持中间件机制,可以方便地扩展功能。路由:灵活的路由机制,支持路径参数、查询参数等多种路由方式。类型安全:利用 Rust 的类型系统,确保代码的类型安全性和健壮性。社区支持:拥有活跃的开发者社区和丰富的文档资源。

大白菜代码的博客 2166

Rust操作MySQL

query函数已经将字节转换为选择的数据类型,因此不需要再转换了。需要注意的是,这里必须明确元组的数据类型(如此处是。函数返回的是Option的结果。所以需将低级的字节转换成想要的类型 如i32,String,这里使用了。其实还可以将查询结果收集到Vec中。Vec中的每个元素都是一个元组。注意,转换后的数据以元组的形式返回,其中每一项和选择列的顺序相同。如果表的列数很多,使用元组容易混淆,更普遍的做法是定义一个结构体。方法获取到新记录的主键id,该方法将返回的一个类型为u64。,其把数据以字节的形式存储。

techdashen的博客 2万+

Rust语言的Web开发

Rust是一种系统编程语言,强调安全、并发和性能。它由Mozilla开发,首次发布于2010年,目标是开发一种兼具高效与安全的语言。Rust提供的所有权(Ownership)系统帮助开发者在编译期捕获许多潜在的内存错误,比如空指针解引用和数据竞争等。Rust在Web开发中具有很大的潜力,为开发者提供了安全、高性能和并发能力的工具。随着Rust生态系统的不断发展,越来越多的框架和库出现,使得Rust的Web开发越来越成熟。凭借其强大的性能和安全性,Rust可以帮助开发者构建更可靠、更高效的Web应用。

2501_90443766的博客 642

Rust个人学习之Rust操作Mysql数据库

如果想使用 mysql 需要在 CargoToml 文件中增加 mysql 的引用。

minibrid的分享页 3930

Rust Axum前后端分离项目-使用sea-orm连接数据库

Sea-ORM 是一个基于 Rust 语言开发的异步 ORM(对象关系映射)框架,专为 Rust 生态设计,旨在简化与关系型数据库的交互。它结合了 sqlx(异步 SQL 执行库)和 SeaQuery(动态 SQL 生成器)的优势,提供类型安全、高性能且符合 Rust 习惯的数据库操作体验。使用 Rust 结构体和宏(derive)定义数据模型,自动生成数据库表结构,代码与数据库 Schema 强关联。

weixin_39141295的博客 1866

【Rust操作MySql】Actix Web 框架结合 MySQL 数据库进行交互

在 Rust 中,使用 Actix Web 框架结合 MySQL 数据库进行交互,通常可以通过 sqlx、diesel、mysql_async 等库实现。 本文就使用 Actix Web + sqlx + MySQL 的最常见组合示例,包括设置、连接池、路由处理和查询执行等进行详细阐述。

景天科技苑 3万+

Rust:连接MySQL

Rust:连接MySQL

cacique111的博客 2794

【Actix Web ORM】Rust ORM框架SeaORM应用实战

Rust 语言以其高性能和内存安全特性逐渐在后端开发中崭露头角。Actix Web 是一个强大而灵活的异步 Web 框架,而 SeaORM 是 Rust 中流行且现代的异步 ORM。 两者结合,可以实现类型安全、性能优越的 Web 应用开发。

景天科技苑 4万+

Rust Web 全栈开发(五):使用 sqlx 连接 MySQL 数据库

Rust Web 全栈开发(五):使用 sqlx 连接 MySQL 数据库

ProgramNovice的博客 902

用 Rust 实现的 Mysql 客户端库。

mysql这个板条箱提供:纯 Rust 中的 MySql 数据库驱动程序;连接池。特征:macOS、Windows 和 Linux 支持;通过nativetls创建的 TLS 支持;MySql文本协议支持,即支持简单的文本查询和文本结果集;MySql 二进制协议支持,即支持预处理语句和二进制结果集;支持多结果集;支持预处理语句的命名参数;准备好的语句的可选每个连接缓存;支持大于 2^24 的 MySql 数据包;支持 Unix 套接字和 Windows 命名管道;支持自定义 LOCAL INFILE 处理程序;支持MySql协议压缩;支持认证插件:mysql_native_password - 用于 v8 之前的 MySql;caching_sha2_password - 适用于 MySql v8 及更高版本。安装将所需版本的 crate 放入Cargo.toml的dependencies部分:[dependencies ]mysql =" *"例子use mysql::* ;use mysql:: prelude::* ; #[derive(Debug, PartialEq, Eq)

ECharts 实现世界地图

效果图 源代码(仅供参考) <template> <div class="echarts"> <div class="className" id="id" style="width: 100%; height: 450px" ref="myEchart" ></div> </div> </template> <script lang="ts"> imp

EsquireY的博客 4872

Rust Web 全栈开发(九):增加教师管理功能

Rust Web 全栈开发(九):增加教师管理功能

ProgramNovice的博客 1295

新四化浪潮下,中年整车诊断工程师的黄金突围路

摘要 本文探讨了汽车电子诊断工程师在当前行业变革中的独特优势与发展路径。作者作为从业者指出,整车诊断岗位在汽车"新四化"浪潮中呈现"逆生长"特性:技术壁垒高、经验复利强、市场需求持续增长。中年工程师凭借丰富的案例积累和实战经验成为行业稀缺人才,薪资水平显著高于同龄段其他技术岗位。文章分析了四条职业发展路径(技术专家、技术管理、细分深耕、横向拓展),并警示了三大职业风险(执行层局限、技术停滞、成果沉淀不足)。最后提出具体建议,强调掌握UDS高阶、DoIP以太网诊断等核心技术的重要性,为中年技术人提供了清晰的职

Soly_kun的博客 309

推荐项目:构建未来web的基石——RealWorld Rust Axum SQLx

推荐项目:构建未来web的基石——RealWorld Rust Axum SQLx 在浩瀚的开源海洋中,有一颗璀璨的技术之星,它集现代编程语言的智慧与效率之大成,为我们展现了一个全面而强大的全栈应用实现框架——RealWorld Rust Axum SQLx。这不仅是对Rust编程语言的一次深度探索,也是将Axum、SQLx与Yew这些明星库结合,创造高效、可靠的Web服务和前端体验的典范。 项...

gitblog_00692的博客 872

URDF模型转换实战:解决SolidWorks到Gazebo的五大常见问题

本文详细解析了从SolidWorks导出URDF模型到Gazebo仿真过程中常见的五大问题及解决方案,包括插件安装、坐标系建立、Link与Joint配置等关键步骤。通过实战经验分享,帮助机器人仿真工程师高效完成模型转换,确保Gazebo仿真准确性。

weixin_29052717的博客 442

论文笔记--Spatial-Temporal Fusion Graph Neural Networks for Traffic Flow Forecasting

Summary 在交通预测问题上,现有的框架通常利用给定的空间邻接图和复杂的机制来建模空间和时间关联。作者认为使用给定的空间邻接图会限制模型的有效时空相关性学习,并且它们通常使用单独的模块来实现空间和时间相关性,或者只使用独立的组件来捕获局部或全局的异质依赖关系,割裂了时空依赖关系。为此作者提出一种时空融合图神经网络用于交通预测。提出一种数据驱动的“时间图”生成方法,以此来补偿空间图可能没有反映的相关性。提出一种新的交通预测方法。 Problem Definition 交通预测中的挑战 以上图为

chenxino的博客 1755

Rust Web 开发终极指南:2026年最新生态全景

🚀 Rust 作为一门系统级编程语言,如今在 Web 开发领域已经展现出惊人的实力!根据 Are We Web Yet 项目的权威评估,Rust Web 生态已经达到了生产就绪状态,不仅功能完善,而且性能卓越。 ## 为什么选择 Rust 进行 Web 开发? Rust 在 Web 开发中具有独特优势:**内存安全、零成本抽象、极致性能**。这些特性使得 Rust 成为构建高并发、高性能 W

gitblog_00290的博客 1021

ArcGIS 10.4 Desktop安装步骤。

步骤 1 :安装 LicenseManager 有了安装包以后,我们进入安装包文件目录,如图所示,首先需要安装ArcGIS license manager,即点击打开图中红色框中exe文件。 PS:安装 LicenseManager,关于 License 的安装路径一般默认即可,因为很小的,装C 盘也无所谓了,当然了自己改了也可以的,但是不建议! 即便修改路径,建议只改盘符,不修改相对路径方式! 如:C:\Program Files (x86)\ArcGIS\License10.6 修改为 D:\.

IT爱好者 2万+

推荐项目:下一代全栈Web框架 - TEO

--- ## 1、项目介绍 在编程世界中,寻找一款高效且易于使用的全栈框架是开发者的重要任务。今天,我们要向您推荐一个创新的Web开发框架——**TEO**。这个框架不仅支持Rust和Node.js两种主流后端语言,而且具备对MySQL, PostgreSQL, SQLite和MongoDB四种数据库的强大支持,让您的开发工作更加得心应手。 官方网站:https://teocloud...

gitblog_00023的博客 662

BLE透传速率调优(二)

摘要:本文分析了蓝牙协议中MDbit=1时应连续发送多包但实际只发一包的问题。通过波形分析和代码调试,发现问题的根源在于接收端出现sync error导致提前关闭接收窗口,而非发送端问题。最终通过调整RF时序参数(将rxpathdly改为13us)解决了TX到RX转换的同步问题,使连接事件能正常发送多个数据包,传输速率提升至70kb/s。文中详细记录了使用逻辑分析仪和trace宏调试的过程,揭示了CEVA芯片设计中TX中断与ACK接收的关联特性。

袁赟的博客 799

教你写出高质量SQL(实践必备)

1、查询SQL尽量不要使用select * ,而是select具体字段。 //反例子 select * from sys_user; //正例子 select id,name from sys_user; 理由: 只取需要的字段,节省资源、减少网络开销;select * 进行查询时,很可能就不会使用到覆盖索引,就会造成回表查询; 覆盖索引:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。 例如: 回表查询:先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记..

ANGEL CITY 9996

Rust-DB/Refinery 开源项目教程

Refinery 是一个用 Rust 编写的迁移工具,旨在帮助开发者管理和执行数据库迁移。它支持多种数据库后端,如 PostgreSQL、MySQL 和 SQLite,使得数据库迁移过程更加简单和高效。 ## 项目快速启动 ### 安装 首先,确保你已经安装了 Rust 和 Cargo。然后,通过 Cargo 安装 Refinery: ```bash cargo install refin...

gitblog_00342的博客 486

H3C BGP联盟实验

bgp,bgp联盟,bgp公认团体属性,

一只懒羊羊 968

Dioxus与数据库集成:全栈Rust应用的数据持久化终极指南

在构建现代全栈应用时,数据持久化是核心需求之一。Dioxus作为一款强大的Rust全栈GUI框架,提供了多种数据库集成方案,让开发者能够轻松实现数据存储和管理。本文将详细介绍如何在Dioxus应用中集成SQLite、PostgreSQL等数据库,构建可靠的数据持久化层。 ## 🔍 为什么选择Dioxus进行全栈开发? Dioxus是一个跨平台的Rust GUI框架,支持Web、桌面、移动端和

gitblog_00851的博客 467

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

上一篇: Rust Web 全栈开发(五):使用 sqlx 连接 MySQL 数据库
下一篇: Rust Web 全栈开发(八):添加功能并重构
UestcXiye
博客等级 码龄8年 9028粉丝 1306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值