数据库在编程中是一个很重要的环节,这里是记录rust
如何操作数据库并以mysql
为主的做简单的使用说明。rust
中对mysql
数据库存有支持的我所知道的crate
:
mysql
单一驱动sqlx
多驱动,异步,不是ORMdiesel
多驱动,异常,ORM因为mysql
的crate
有点单一,这里主要是说明sqlx
及diesel
的使用,分两篇记录。本都吃是以sqlx
为说明
在这过程的走了不少不能言语的弯路主要有以下两点:
postgres
(英文不好)select
出来的数据不直观。不像php
那样直接一个关联数据解决所有问题sqlx
的sql是原始的,我所知道的是它不像ORM那样,能通过一系列的方法组合成相应的sql,sql都是手写的。是不是用ORM,看自己的需求进行选择
crate
依赖(Cargo.toml文件)[dependencies]
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.6", features = [
"runtime-tokio-native-tls",
"mysql",
"chrono",
"json",
"macros",
"decimal"
] }
dotenvy = "0.15.6"
chrono = { version = "0.4.23", features = ["serde"] }
sqlx-cli = "0.6.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rust_decimal = "1.28.0"
这里我使用的运行时是tokio
,它好像支持三种运行时,另外两种是Async-std
(runtime-async-std-native-tls),Actix-web
(runtime-actix-native-tls),不同的运行里相应的要在sqlx
开启相应的特性支持。在这里有一个很重要的特性macros
,如果大量使用宏也就是query*!
,而且也非常方面查询,可以让我获取类似php
中操作数据库的感觉,所以这个特性很重要。
通过下标来获取数据
.env
文件内容
DATABASE_URL=mysql://root:root@localhost:3306/test
main.rs
文件内容
use chrono::{NaiveDateTime}; // 处理 数据库中 datetime 类型
use sqlx::Row; // get 方法的 trait
use sqlx::mysql::{MySqlRow, MySqlPoolOptions};
use dotenvy::dotenv;
use std::{env};
use serde::{Deserialize, Serialize};
use serde_json::Value; // 处理 数据库中 json 类型
use rust_decimal::Decimal; // 处理 数据库中 decimal 类型
#[derive(Debug)]
struct RawRawData {
id: i32,
title: String,
subtitle: String,
content: String,
tag: Value,
create_time: NaiveDateTime,
normal_time: i32,
vip_pay: Decimal,
normal_pay: f32,
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenv().ok();
let url = env::var("DATABASE_URL").expect("没有设置数据信息");
let pool = MySqlPoolOptions::new().connect(&url).await?;
let rows: Vec<MySqlRow> = sqlx::query("select id,title from test").fetch_all(&pool).await?;
let mut data: Vec<RawRawData> = vec![];
for row in rows.iter() {
let id: i32 = row.get(0);
let title: String = row.get(1);
let subtitle: String = row.get(2);
let content: String = row.get(3);
let tag: Value = row.get(4);
let create_time: NaiveDateTime = row.get(5);
let normal_time: i32 = row.get(6);
let vip_pay: Decimal = row.get(7);
let normal_pay: f32 = row.get(8);
data.push(RawRawData{
id,
title,
subtitle,
content,
tag,
create_time,
normal_time,
vip_pay,
normal_pay
});
}
Ok(())
}
单纯的查询出来的 rows
数据打印出来有点初一看看不明白,没有直观性的 key=>value
的形式,感觉是因为强类的原因,不能单纯的当做 String=>String
来处理,所以数据库中的类型也要与rust
中的类型相一一对应。上面的代码是通过索引(get
)的方法来获取相应的值,这个方法依赖 Row
trait,这个是关键
处理后的数据
[
RawRawData {
id: 1,
title: "111",
subtitle: "1111",
content: "11111",
tag: Array [],
create_time: 2023-02-02T03:06:17,
normal_time: 1675278415,
vip_pay: 10.20,
normal_pay: 10.0,
},
RawRawData {
id: 2,
title: "2222",
subtitle: "222",
content: "2222",
tag: Array [
String("111"),
String("222"),
],
create_time: 2023-02-02T03:06:17,
normal_time: 1675278415,
vip_pay: 10.20,
normal_pay: 10.0,
},
]
原始数据
MySqlRow { row: Row { storage: b"\0\0\x01\0\0\0\x03111\x041111\x0511111\x02[]\x07\xe7\x07\x02\x02\x03\x06\x11O\xb8\xdac\x0510.20\0\0 A", values: [Some(2..6), Some(7..10), Some(11..15), Some(16..21), Some(22..24), Some(24..32), Some(32..36), Some(37..42), Some(42..46)] }, format: Binary, columns: [MySqlColumn { ordinal: 0, name: id, type_info: MySqlTypeInfo { type: Long, flags: NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT, char_set: 63, max_size: Some(11) }, flags: Some(NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT) }, MySqlColumn { ordinal: 1, name: title, type_info: MySqlTypeInfo { type: String, flags: (empty), char_set: 224, max_size: Some(80) }, flags: Some((empty)) }, MySqlColumn { ordinal: 2, name: subtitle, type_info: MySqlTypeInfo { type: VarString, flags: (empty), char_set: 224, max_size: Some(1020) }, flags: Some((empty)) }, MySqlColumn { ordinal: 3, name: content, type_info: MySqlTypeInfo { type: Blob, flags: BLOB, char_set: 224, max_size: Some(262140) }, flags: Some(BLOB) }, MySqlColumn { ordinal: 4, name: tag, type_info: MySqlTypeInfo { type: Json, flags: BLOB | BINARY, char_set: 63, max_size: Some(4294967295) }, flags: Some(BLOB | BINARY) }, MySqlColumn { ordinal: 5, name: create_time, type_info: MySqlTypeInfo { type: Datetime, flags: BINARY, char_set: 63, max_size: Some(19) }, flags: Some(BINARY) }, MySqlColumn { ordinal: 6, name: normal_time, type_info: MySqlTypeInfo { type: Long, flags: (empty), char_set: 63, max_size: Some(11) }, flags: Some((empty)) }, MySqlColumn { ordinal: 7, name: vip_pay, type_info: MySqlTypeInfo { type: NewDecimal, flags: (empty), char_set: 63, max_size: Some(12) }, flags: Some((empty)) }, MySqlColumn { ordinal: 8, name: normal, type_info: MySqlTypeInfo { type: Float, flags: (empty), char_set: 63, max_size: Some(10) }, flags: Some((empty)) }], column_names: {title: 1, id: 0, content: 3, vip_pay: 7, create_time: 5, subtitle: 2, tag: 4, normal_time: 6, normal: 8} }]
索引这个方法,在字段少的情况下还行,多的时间,代码量多,还要 for
二次处理,不是很方便。在已定义的结构中 RawRawData
所要的字段及对应的类型都有了,我们只要为它加一个 sqlx::FromRow
特性就可再配合 query_as
方法就可以实现自动转化,达到想要的效果。
代码量减少的同时还更加符合人性化,这种使用方法相当不错,推荐使用,要注意点是查询出来的字段一定要多于结构的字段
结构变化(部分)
#[derive(Debug, sqlx::FromRow)]
struct RawRawData {
id: i32,
title: String,
subtitle: String,
content: String,
tag: Value,
create_time: NaiveDateTime,
normal_time: i32,
vip_pay: Decimal,
normal_pay: f32,
}
查询部分
let data = sqlx::query_as::<_, RawRawData>("select * from test").fetch_all(&pool).await?;
println!("{:?}", data);
结果
[
RawRawData {
id: 1,
title: "111",
subtitle: "1111",
content: "11111",
tag: Array [],
create_time: 2023-02-02T03:06:17,
normal_time: 1675278415,
vip_pay: 10.20,
normal_pay: 10.0,
},
RawRawData {
id: 2,
title: "2222",
subtitle: "222",
content: "2222",
tag: Array [
String("111"),
String("222"),
],
create_time: 2023-02-02T03:06:17,
normal_time: 1675278415,
vip_pay: 10.20,
normal_pay: 10.0,
},
]
宏方法查询来的结果不需要由手动转化,是一种比较接近 php
关联查询来的结果。可以通过字段名直接访问数据,要注意的一点是,它有很多数据类型的值都是 Option
类型(我这边除了主键是数字,其它都是 Option
),主要有两个方法 query!
及 query_as!
部分代码
let rows = sqlx::query!("select * from test")
.fetch_all(&pool).await?;
println!("{:#?}", rows);
结果
[
Record {
id: 1,
title: Some(
"111",
),
subtitle: Some(
"1111",
),
content: Some(
"11111",
),
tag: Some(
Array [],
),
create_time: Some(
2023-02-02T03:06:17,
),
normal_time: Some(
1675278415,
),
vip_pay: Some(
10.20,
),
normal_pay: Some(
10.0,
),
},
Record {
id: 2,
title: Some(
"2222",
),
),
create_time: Some(
2023-02-02T03:06:17,
),
normal_time: Some(
1675278415,
),
vip_pay: Some(
10.20,
),
normal_pay: Some(
10.0,
),
},
]
上面说的都是 query
也就是增删改查中的查。查关注的点是数据,增删改关注的点是影响数量,使用的方法很简单,只返回影响的数量及最新插入的id
代码
// 增加
let data = sqlx::query("INSERT INTO `test`.`test`(`id`, `title`, `subtitle`, `content`, `tag`, `create_time`, `normal_time`, `vip_pay`, `normal_pay`) VALUES (4, '2222', '222', '2222', '[\"111\", \"222\"]', '2023-02-02 03:06:17', 1675278415, 10.20, 10);
").execute(&pool).await?;
println!("{:#?}", data);
// 删除
let data = sqlx::query("DELETE FROM `test` where id = ?")
.bind(4)
.execute(&pool).await?;
println!("{:#?}", data);
对应的结果
MySqlQueryResult {
rows_affected: 1,
last_insert_id: 5,
}
MySqlQueryResult {
rows_affected: 1,
last_insert_id: 0,
}
总的来说,查询常用的方法就两个 fetch_all
及 fetch_one
上面用的都是 fetch_all
,fetch_one
的用法同时。而对于增删改则只有一个execute
方法。
多数情况下sql
是有条件参数的,条件一多,手动组合sql会有点麻烦,那么对于sqlx
来说,怎么处理会比较好?
The End。