Web框架常會搭配資料庫一起使用,Rocket框架當然也有一定程度的資料庫支援。雖然我們可以直接使用Rocket框架提供的應用程式狀態機制來註冊任意套件提供的資料庫實體,但是在程式撰寫上會比較麻煩一點,像是資料庫的位址、帳密等資訊,我們可能就必須要寫死在程式碼內,或是自己再另外開發出可以讓Rocket框架從外部讀取資料庫設定值的功能。Rocket官方提供的rocket_db_pools
套件,能替Rocket框架添加資料庫支援,可以快速地將資料庫實體變成請求守衛來取用,也可以將資料庫的設定融入至Rocket框架的設定中。
rocket_db_pools
套件根據不同的資料庫類種和驅動程式,分出不同的特色,並提供不同的型別來支援它們。在應用時,設定檔和程式碼的基本架構如下:
rocket_db_pools = { version = "0.1.0", features = ["資料庫特色"] }
[default.databases.名稱]
url = "資料庫URL"
#[macro_use]
extern crate rocket;
use rocket_db_pools::{Connection, Database};
#[derive(Database)]
#[database("名稱")]
struct 資料庫結構體(資料庫Pool型別);
#[launch]
fn rocket() -> _ {
rocket::build().attach(資料庫結構體::init())
}
在路由處理程序中可以使用rocket_db_pools::Connection
結構體來包裹我們的資料庫結構體作為請求守衛。如下:
#[HTTP請求方法("路徑")]
fn handler(變數名稱: Connection<資料庫結構體>) -> 要回應的資料型別 {
處理請求的程式區塊
}
當HTTP請求成功交給上面的處理程序處理時,就會拿到資料庫結構體所包裹的Pool的資料庫連線。至於資料庫Pool型別具體到底會用到哪個型別,請參考下表:
資料庫種類 | 驅動程式 | 資料庫特色 | 資料庫型別 |
---|---|---|---|
Postgres | deadpool |
deadpool_postgres |
deadpool_postgres::Pool |
Redis | deadpool |
deadpool_redis |
deadpool_redis::Pool |
Postgres | sqlx |
sqlx_postgres |
sqlx::PgPool |
MySQL | sqlx |
sqlx_mysql |
sqlx::MySqlPool |
SQLite | sqlx |
sqlx_sqlite |
sqlx::SqlitePool |
MongoDB | mongodb |
mongodb |
mongodb::Client |
Postgres | diesel |
diesel_postgres |
diesel::PgPool |
MySQL | diesel |
diesel_mysql |
diesel::MysqlPool |
實際演練
MySQL
目標:讓Rocket應用程式連接MySQL資料庫,並完成資料的存取。
首先建立新的MySQL資料庫和使用者。使用者名稱和資料庫名稱均為magic_rocket
,使用者密碼為B+Mj~#mHE5W@
。
在一般的shell下執行:
mysql -u root -p
在MySQL的shell下執行以下SQL,來建立出資料庫和資料表:
CREATE DATABASE `magic_rocket`;
在MySQL的shell下執行以下SQL,來建立出可以存取剛才建立出來的資料庫的使用者:
CREATE USER 'magic_rocket'@'localhost' IDENTIFIED BY '4M{}^;dkn7Eg';
GRANT ALL ON `magic_rocket`.* TO 'magic_rocket'@'localhost';
# GRANT ALL PRIVILEGES ON `magic_rocket`.* TO 'magic_rocket'@'localhost' IDENTIFIED BY '4M{}^;dkn7Eg';
然後開始撰寫Rust程式專案吧!
[dependencies]
rocket = { version = "0.5.0" }
rocket_db_pools = { version = "0.1.0", features = ["sqlx_mysql"] }
[default.databases.example]
url = "mysql://magic_rocket:4M{}^;dkn7Eg@localhost:3306/magic_rocket"
#[macro_use]
extern crate rocket;
use rocket::fairing::AdHoc;
use rocket_db_pools::{
sqlx::{self, Row},
Connection, Database,
};
#[derive(Database)]
#[database("example")]
struct MyDatabase(sqlx::MySqlPool);
#[put("/<key>/<value>")]
async fn put(mut db: Connection<MyDatabase>, key: String, value: String) -> &'static str {
sqlx::query("INSERT INTO `example` (`key`, `value`) VALUES (?, ?)")
.bind(key)
.bind(value)
.execute(&mut **db)
.await
.unwrap();
"ok"
}
#[get("/<key>")]
async fn get(mut db: Connection<MyDatabase>, key: String) -> Option<String> {
let result = sqlx::query("SELECT `value` FROM `example` WHERE `key` = ?")
.bind(key)
.fetch_optional(&mut **db)
.await
.unwrap();
result.map(|row| row.get("value"))
}
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(MyDatabase::init())
.attach(AdHoc::try_on_ignite("MySQL Initializer", |rocket| {
Box::pin(async move {
let mut connection = match MyDatabase::fetch(&rocket) {
Some(pool) => match pool.acquire().await {
Ok(connection) => connection,
Err(_) => return Err(rocket),
},
None => return Err(rocket),
};
let result = sqlx::query(
"CREATE TABLE IF NOT EXISTS `example` ( `id` INT NOT NULL AUTO_INCREMENT, \
`key` varchar(100), `value` TEXT , PRIMARY KEY (`id`), UNIQUE KEY (`key`) )",
)
.execute(&mut *connection)
.await;
match result {
Ok(_) => Ok(rocket),
Err(_) => Err(rocket),
}
})
}))
.mount("/", routes![put, get])
}
以上的Rust程式,我們除了將MyDatabase
的整流片註冊給Rocket之外,還另外再註冊了一個AdHoc
整流片來初始化MySQL資料庫。
MongoDB
目標:讓Rocket應用程式連接MongoDB資料庫,並完成資料的存取。
使用的MongoDB資料庫資料庫名稱為magic_rocket
,不必事先建立出來。
直接開始撰寫Rust程式專案吧!
[dependencies]
rocket = { version = "0.5.0" }
rocket_db_pools = { version = "0.1.0", features = ["mongodb"] }
[default.databases.example]
url = "mongodb://localhost:27017/magic_rocket"
#[macro_use]
extern crate rocket;
use rocket::fairing::AdHoc;
use rocket_db_pools::{
mongodb::{
self,
bson::{doc, Bson, Document},
options::IndexOptions,
IndexModel,
},
Connection, Database,
};
#[derive(Database)]
#[database("example")]
struct MyDatabase(mongodb::Client);
#[put("/<key>/<value>")]
async fn put(db: Connection<MyDatabase>, key: String, value: String) -> &'static str {
let db = db.default_database().unwrap();
let collection_example = db.collection("example");
collection_example.insert_one(doc! {"key": key, "value": value}, None).await.unwrap();
"ok"
}
#[get("/<key>")]
async fn get(db: Connection<MyDatabase>, key: String) -> Option<String> {
let db = db.default_database().unwrap();
let collection_example = db.collection("example");
let result: Option<Document> =
collection_example.find_one(Some(doc! {"key": key}), None).await.unwrap();
match result {
Some(mut doc) => {
if let Bson::String(value) = doc.remove("value").unwrap() {
Some(value)
} else {
panic!("Value should be a string!")
}
},
None => None,
}
}
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(MyDatabase::init())
.attach(AdHoc::try_on_ignite("MongoDB Initializer", |rocket| {
Box::pin(async move {
let db = match MyDatabase::fetch(&rocket) {
Some(client) => match client.default_database() {
Some(database) => database,
None => return Err(rocket),
},
None => return Err(rocket),
};
let collection_example = db.collection::<Document>("example");
let result = collection_example
.create_index(
{
let mut index = IndexModel::default();
let mut options = IndexOptions::default();
options.unique = Some(true);
index.keys = doc! {"key": 1};
index.options = Some(options);
index
},
None,
)
.await;
match result {
Ok(_) => Ok(rocket),
Err(_) => Err(rocket),
}
})
}))
.mount("/", routes![put, get])
}
同樣地,以上的Rust程式,我們除了將MyDatabase
的整流片註冊給Rocket之外,還另外再註冊了一個AdHoc
整流片來初始化MongoDB資料庫。
總結
這個章節介紹了在Rocket框架存取資料庫的方式,也使我們更能體會Rocket框架的整流片以及請求守衛究竟是有多麼便利。結合了資料庫的Rocket應用程式,就可以拿來實現更為龐大的Web架構,一旦商業邏輯愈來愈複雜,就會使得程式開發人員犯錯的機率增高,所以下一章我們要學習撰寫測試(testing),來自動驗證程式邏輯的正確性。
最後,除了rocket_db_pools
套件之外,Rocket官方其實還有提供rocket_sync_db_pools
套件,它支援採用阻塞式I/O的資料庫驅動程式,如今不是很建議使用了。但rocket_sync_db_pools
套件有支援rocket_db_pools
套件所沒支援的資料庫,如果真的有需要的話,還是可以考慮將其加入專案中使用,不過本系列文章就不提這個套件的用法了。
下一章:測試。