» Rust:使用Rocket构建REST API » 2. 开发 » 2.6 数据库:MySQL

数据库:MySQL

SQLite 只是为了效果演示。如果你需要一个生产环境可用的数据库,可尝试 MySQLmongoDB

切换到 MySQL

  1. 在你的机器上安装 MySQL 并启动。

注意:在实际生产项目中,记得给表中字段创建所需的索引。

  1. 添加 mysql 依赖:
cargo add mysql

提示: 你还可以选择 sqlx 或者 Diesel

Cargo.toml 中的自动更新:

@@ -8,6 +8,7 @@ edition = "2021"
 [dependencies]
 chrono = { version = "0.4.35", features = ["serde"] }
 lazy_static = "1.4.0"
+mysql = "24.0.0"
 rocket = { version = "0.5.0", features = ["json"] }
 rusqlite = "0.31.0"
 serde = { version = "1.0.197", features = ["derive"] }
  1. 更新代码。

添加 infrastructure/database/mysql.rs:

use std::error::Error;

use chrono::Utc;
use mysql::prelude::Queryable;
use mysql::{Error as MySQLError, Pool};

use crate::domain::gateway::BookManager;
use crate::domain::model;

pub struct MySQLPersistence {
    pool: Pool,
}

impl MySQLPersistence {
    pub fn new(dsn: &str) -> Result<Self, MySQLError> {
        let pool = Pool::new(dsn)?;
        Ok(MySQLPersistence { pool })
    }
}

impl BookManager for MySQLPersistence {
    fn create_book(&self, b: &model::Book) -> Result<u32, Box<dyn Error>> {
        let mut conn = self.pool.get_conn()?;
        conn.exec::<usize, &str, (String, String, String, String, String, u32)>(
            "INSERT INTO books (title, author, published_at, description, isbn, total_pages)
             VALUES (?, ?, ?, ?, ?, ?)",
            (
                b.title.clone(),
                b.author.clone(),
                b.published_at.clone(),
                b.description.clone(),
                b.isbn.clone(),
                b.total_pages,
            ),
        )?;
        Ok(conn.last_insert_id() as u32)
    }

    fn update_book(&self, id: u32, b: &model::Book) -> Result<(), Box<dyn Error>> {
        let mut conn = self.pool.get_conn()?;
        conn.exec::<usize, &str, (String, String, String, String, String, u32, String, u32)>(
            "UPDATE books SET title = ?, author = ?, published_at = ?, description = ?, isbn = ?, total_pages = ?, updated_at = ?
            WHERE id = ?",
            (b.title.clone(), b.author.clone(), b.published_at.clone(), b.description.clone(), b.isbn.clone(), b.total_pages, Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),  id),
        )?;
        Ok(())
    }

    fn delete_book(&self, id: u32) -> Result<(), Box<dyn Error>> {
        let mut conn = self.pool.get_conn()?;
        conn.exec::<usize, &str, (u32,)>("DELETE FROM books WHERE id = ?", (id,))?;
        Ok(())
    }

    fn get_book(&self, id: u32) -> Result<Option<model::Book>, Box<dyn Error>> {
        let mut conn = self.pool.get_conn()?;
        let books = conn.query_map(
            format!("SELECT * FROM books WHERE ID = {}", id),
            |(
                id,
                title,
                author,
                published_at,
                description,
                isbn,
                total_pages,
                created_at,
                updated_at,
            ): (
                u64,
                String,
                String,
                String,
                String,
                String,
                u64,
                String,
                String,
            )| {
                model::Book {
                    id: id as u32,
                    title,
                    author,
                    published_at,
                    description,
                    isbn,
                    total_pages: total_pages as u32,
                    created_at,
                    updated_at,
                }
            },
        )?;
        Ok(books.first().cloned())
    }

    fn get_books(&self) -> Result<Vec<model::Book>, Box<dyn Error>> {
        let mut conn = self.pool.get_conn()?;
        let books = conn.query_map(
            "SELECT * FROM books",
            |(
                id,
                title,
                author,
                published_at,
                description,
                isbn,
                total_pages,
                created_at,
                updated_at,
            ): (
                u64,
                String,
                String,
                String,
                String,
                String,
                u64,
                String,
                String,
            )| {
                model::Book {
                    id: id as u32,
                    title,
                    author,
                    published_at,
                    description,
                    isbn,
                    total_pages: total_pages as u32,
                    created_at,
                    updated_at,
                }
            },
        )?;
        Ok(books)
    }
}

DSN 表示数据源名称(Data Source Name)。它是用于向应用程序提供数据实例连接信息的字符串。

调整 infrastructure/config/mod.rs 以导出符号:

@@ -1,3 +1,3 @@
-mod sqlite;
+mod mysql;
 
-pub use sqlite::SQLitePersistence;
+pub use mysql::MySQLPersistence;

DBConfig 结构体添加 dsn 配置项,infrastructure/config/mod.rs:

@@ -9,6 +9,7 @@ pub struct Config {
 #[derive(Debug, Deserialize, Serialize)]
 pub struct DBConfig {
     pub file_name: String,
+    pub dsn: String,
 }
 
 #[derive(Debug, Deserialize, Serialize)]

更新 WireHelper 以切换依赖,application/wire_helper.rs:

@@ -5,12 +5,12 @@ use crate::infrastructure::database;
 use crate::infrastructure::Config;
 
 pub struct WireHelper {
-    persistence: Arc<database::SQLitePersistence>,
+    persistence: Arc<database::MySQLPersistence>,
 }
 
 impl WireHelper {
     pub fn new(c: &Config) -> Result<Self, Box<dyn std::error::Error>> {
-        let persistence = Arc::new(database::SQLitePersistence::new(&c.db.file_name)?);
+        let persistence = Arc::new(database::MySQLPersistence::new(&c.db.dsn)?);
         Ok(WireHelper { persistence })
     }

main.rs 中放入 dsn 值:

@@ -14,6 +14,7 @@ fn rocket() -> _ {
         app: ApplicationConfig { port: 8000 },
         db: DBConfig {
             file_name: "test.db".to_string(),
+            dsn: "mysql://test_user:test_pass@127.0.0.1:3306/lr_book".to_string(),
         },
     };
     let wire_helper = application::WireHelper::new(&c).expect("Failed to create WireHelper");

瞧!你的 API 服务器现在由 MySQL 驱动啦!