Rust 操作 MySQL

查詢

本部分是對「Rust 入門系列」Rust 中使用 MySQL[1] 的學習與記錄

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) NOT NULL,
  `age` int(11) NOT NULL,
  `id_card` varchar(128) NOT NULL,
  `last_update` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入測試數據
insert into student (name, age, id_card, last_update) values ('張三', 23, '123456789X', CURRENT_DATE());
insert into student (name, age, id_card, last_update) values ('李四', 24, '8382353902', CURRENT_DATE())

注意,mysql[2] 這個 crate 新版本 demo 有問題,文檔的更新速度跟不上代碼的修改腳步..

需要指定版本:

[dependencies]
mysql = "20.0.0" #通配符*表示可以使用任何版本,通常會拉取最新版本;此處需要指定,不使用最新版本

流式查詢

use chrono::prelude::*;// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接

    conn.query_iter("select * from student")
        .unwrap()
        .for_each(|row| {
            let r: (i32, String, i32, String, NaiveDate) = from_row(row.unwrap());
            println!("{}, {},{},{}, {:?}", r.0, r.1, r.2, r.3, r.4);
        });
}

row 的類型是mysql_common::row::Row,其把數據以字節的形式存儲。

所以需將低級的字節轉換成想要的類型 如 i32,String,這裏使用了from_row。注意,轉換後的數據以元組的形式返回,其中每一項和選擇列的順序相同。

輸出:

1, 張三,23,123456789X, 2022-04-26
2, 李四,24,8382353902, 2022-04-26

聚合查詢結果

其實還可以將查詢結果收集到 Vec 中。Vec 中的每個元素都是一個元組。

query 函數已經將字節轉換爲選擇的數據類型,因此不需要再轉換了。需要注意的是,這裏必須明確元組的數據類型 (如此處是 Vec<(i32, String, i32, String, NaiveDate)>)。否則,編譯器沒辦法做轉換。

use chrono::prelude::*;// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接

// 輸出到Vec
    let res: Vec<(i32, String, i32, String, NaiveDate)=
        conn.query("select * from student").unwrap();
    for r in res {
        println!("{}, {},{},{}, {:?}", r.0, r.1, r.2, r.3, r.4);
    }
}

映射結果到結構體

如果表的列數很多,使用元組容易混淆,更普遍的做法是定義一個結構體。

如下定義一個 Student 結構體, 然後可以用query_map將查詢結果映射到 Student 中。

不需要指定數據類型,編譯器會根據 Student 類型自動推導

use chrono::prelude::*;// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接

    // 將結果映射到提前定義好的結構體
    struct Student {
        id: u64,
        name: String,
        age: u16,
        id_card: String,
        last_changed_on: NaiveDate,
    }

    let res = conn.query_map(
        "select * from student",
        |(id, name, age, id_card, update)| Student {
            id: id,
            name: name,
            age: age,
            id_card: id_card,
            last_changed_on: update,
        },
    ).expect("Query failed.");

    for i in res {
        println!(
            "{}, {},{},{}, {:?}",
            i.id, i.name, i.age, i.id_card, i.last_changed_on
        )
    }
}

單條數據查詢

查詢特定數據行,可能會出現下面幾種情況:

所以,使用query_first函數返回的是 Option 的結果。需要將其解包兩次纔可以獲取實際的行數據:

use chrono::prelude::*;// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接


    struct Student {
        id: u64,
        name: String,
        age: u16,
        id_card: String,
        last_changed_on: NaiveDate,
    }

    // 條件查詢,查詢單個數據
    let res = conn.query_first("select * from student where name = '張三'")
        .map(
            // Unpack Result
            |row| {
                row.map(|(id, name, age, id_card, update)| Student {
                    id: id,
                    name: name,
                    age: age,
                    id_card: id_card,
                    last_changed_on: update,
                })
            },
        );

    match res.unwrap() {
        Some(student) => println!(
            "{}, {},{},{}, {:?}",
            student.id, student.name, student.age, student.id_card, student.last_changed_on
        ),
        None => println!("Sorry no student found."),
    }
}

命名參數的使用

use chrono::prelude::*;// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接


    struct Student {
        id: u64,
        name: String,
        age: u16,
        id_card: String,
        last_changed_on: NaiveDate,
    }

    let res = conn
        .exec_first(
            "select * from student where name = :name",
            params! {
                "name" ="李四"
            },
        )
        .map(
            // Unpack Result
            |row| {
                row.map(|(id, name, age, id_card, update)| Student {
                    id: id,
                    name: name,
                    age: age,
                    id_card: id_card,
                    last_changed_on: update,
                })
            },
        );

    match res.unwrap() {
        Some(student) => println!(
            "{}, {},{},{}, {:?}",
            student.id, student.name, student.age, student.id_card, student.last_changed_on
        ),
        None => println!("Sorry no student found."),
    }

}

寫操作

本部分是對 Rust 使用 MySQL 數據庫 02[3] 的學習與記錄

插入新數據

use chrono::prelude::*;
// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接


    conn.exec_drop(
        "INSERT INTO student (name, age, id_card, last_update) VALUES (:name, :age, :id_card, :last_update)",
        params! {
        "name" ="王五",
        "age" => 28,
        "id_card" ="66666688",
        "last_update" => today(),
}).unwrap();
}

fn today() -> NaiveDate {
    let l = Local::today();

    NaiveDate::from_ymd(l.year(), l.month(), l.day())
}

和上面一節一樣,命名參數在這裏使用了 params 宏的語法

exec_drop方法中的 drop 表示沒有返回結果

用於執行插入 / 更新 / 刪除的 sql

使用預編譯語句

使用conn.prep將 sql 編譯成預編譯語句。

use chrono::prelude::*;
// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接


    let stmt = conn.prep("INSERT INTO student (name, age, id_card, last_update) VALUES (:name, :age, :id_card, :last_update)")
        .unwrap();

    for i in 1..10 {
        conn.exec_drop(&stmt, params! {
         "name" ="dashen",
         "age" => 18 + i,
         "id_card" ="1234565X",
         "last_update" => NaiveDate::from_ymd(2017, 05, 04),
     }).unwrap()
    }
}

獲取生成的主鍵 id

可以通過conn.last_insert_id()方法獲取到新記錄的主鍵 id,該方法將返回的一個類型爲 u64 的值

use chrono::prelude::*;
// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接

    conn.exec_drop("INSERT INTO student (name, age, id_card, last_update) VALUES (:name, :age, :id_card, :last_update)", params! {
    "name" ="fliter",
    "age" => 29,
    "id_card" ="88888888",
    "last_update" => NaiveDate::from_ymd(2022, 05, 04),
}).unwrap();

    println!("新插入的記錄的主鍵爲: {}", conn.last_insert_id())
}
新插入的記錄的主鍵爲: 13

更新和刪除

類似於插入操作

use chrono::prelude::*;
// 用來處理日期
use mysql::*;
use mysql::prelude::*;

fn main() {
    let url = "mysql://root:12345678@localhost:3306/shuang";
    let pool = Pool::new(url).unwrap(); // 獲取連接池


    let mut conn = pool.get_conn().unwrap();// 獲取鏈接


    let stmt = conn.prep("update student set )
        .unwrap();

    conn.exec_drop(&stmt, params! {
     "name" => "新名字",
     "last_update" => NaiveDate::from_ymd(2038, 12, 31),
     "id" => 10,
}).unwrap();


    let stmt = conn.prep("delete from student where id=:id").unwrap();

    conn.exec_drop(&stmt, params! {
    "id" => 12,
}).unwrap();
}

參考資料

[1]

「Rust 入門系列」Rust 中使用 MySQL: https://rustmagazine.github.io/rust_magazine_2021/chapter_3/rust-mysql.html

[2]

mysql: https://crates.io/crates/mysql

[3]

Rust 使用 MySQL 數據庫 02: https://www.modb.pro/db/179746

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/VRlydU9-zTMNHja6mrIySg