当前位置: 首页 > news >正文

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

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

  • Rust Web 全栈开发(六):在 Web 项目中使用 MySQL 数据库
    • 配置项目
    • 修改 AppState
    • 修改 Course
    • 数据库准备
    • 连接请求
    • 读取 MySQL 数据库
    • 其他修改
    • 测试
    • 尾声

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, timeFROM courseWHERE 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, timeFROM courseWHERE 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 语句的写法),请读者仔细比对。

http://www.dtcms.com/a/275509.html

相关文章:

  • 前端note
  • 【推荐】前端低端机和弱网环境下性能优化
  • 前端面试专栏-算法篇:24. 算法时间与空间复杂度分析
  • 在前端开发中关于reflow(回流)和repaint(重绘)的几点思考
  • MySQL 中图标字符存储问题探究:使用外挂法,毕业论文——仙盟创梦IDE
  • AI驱动的大前端内容创作与个性化推送:资讯类应用实战指南
  • 容器化改造避坑指南:传统应用迁移K8s的10个关键节点(2025实战复盘)
  • CSS flex
  • Capsule Networks:深度学习中的空间关系建模革命
  • GGE Lua 详细教程
  • 《Java Web程序设计》实验报告四 Java Script前端应用和表单验证
  • 基于Java的Markdown到Word文档转换工具的实现
  • 基于大模型的鼻咽癌全周期预测及诊疗优化研究报告
  • EPLAN 电气制图(七):电缆设计全攻略
  • 系统学习Python——并发模型和异步编程:基础实例-[使用进程实现旋转指针]
  • 代码训练LeetCode(45)旋转图像
  • 【算法笔记】7.LeetCode-Hot100-图论专项
  • 【node/vue】css制作可3D旋转倾斜的图片,朝向鼠标
  • 每日算法刷题Day46 7.12:leetcode前缀和3道题和差分2道题,用时1h30min
  • 代码训练LeetCode(46)旋转图像
  • Python爬虫实战:研究python-docx库相关技术
  • AI软件出海SEO教程
  • 26. 删除有序数组中的重复项
  • Eureka实战
  • 2025.7.12总结
  • 车载以太网-TTL
  • BaseDao 通用更新方法设计与实现
  • Qt:QCustomPlot类介绍
  • Python问题记录`No module named ‘matplotlib‘` 问题解决方案
  • 精密模具大深径比微孔尺寸检测方案 —— 激光频率梳 3D 轮廓检测