การผสานรวมฐานข้อมูล
เก็บข้อมูลให้คงอยู่
การผสานรวมฐานข้อมูล เป็นบทเรียน Learn Rust Coding ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Learn Rust Coding และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน
การจัดเก็บข้อมูลถาวร
จนถึงตอนนี้ API ของเราเก็บข้อมูลไว้ในหน่วยความจำ ซึ่งจะหายไปเมื่อเริ่มระบบใหม่ บริการจริงจึงต้องใช้ฐานข้อมูล ในบทเรียนนี้ เราจะเชื่อม API ของ Axum กับ PostgreSQL โดยใช้sqlx ซึ่งเป็นชุดเครื่องมือ SQL แบบอะซิงโครนัสสำหรับ Rust ที่ตรวจสอบได้ขณะคอมไพล์
คุณจะได้เรียนรู้เกี่ยวกับกลุ่มการเชื่อมต่อ คำสั่งค้นข้อมูล การจับคู่แถวกับโครงสร้าง และการใช้กลุ่มการเชื่อมต่อเป็นสถานะร่วม
การเพิ่ม sqlx
เพิ่ม sqlx พร้อมความสามารถที่ต้องใช้ ได้แก่ ระบบรันไทม์ TLS และไดรเวอร์ฐานข้อมูล ด้านล่างเป็นการตั้งค่า Postgres ที่ใช้ Tokio
// Cargo.toml
// [dependencies]
// sqlx = { version = "0.7", features = [
// "runtime-tokio", "tls-rustls", "postgres", "macros"
// ] }กลุ่มการเชื่อมต่อ
การเปิดการเชื่อมต่อใหม่สำหรับทุกคำขอช้า กลุ่มการเชื่อมต่อจะเก็บชุดการเชื่อมต่อที่นำกลับมาใช้ใหม่ได้ PgPoolOptions สร้างกลุ่มจาก URL ฐานข้อมูล กลุ่มนี้โคลนได้อย่างมีต้นทุนต่ำ (ภายในใช้การนับจำนวนการอ้างอิง) จึงเหมาะอย่างยิ่งสำหรับใช้เป็นสถานะร่วม
use sqlx::postgres::PgPoolOptions;
async fn make_pool(url: &str) -> sqlx::PgPool {
PgPoolOptions::new()
.max_connections(5)
.connect(url)
.await
.expect("failed to connect")
}ใช้กลุ่มการเชื่อมต่อเป็นสถานะแอปพลิเคชัน
ส่งกลุ่มการเชื่อมต่อให้ Axum ด้วย .with_state(pool) จากนั้นตัวจัดการจะรับ State(pool): State<PgPool> เนื่องจากการโคลนกลุ่มมีต้นทุนต่ำ ทุกคำขอจึงใช้การเชื่อมต่อพื้นฐานชุดเดียวกัน
use axum::{routing::get, Router};
use sqlx::PgPool;
fn build(pool: PgPool) -> Router {
Router::new()
.route("/todos", get(list_todos))
.with_state(pool)
}การเรียกใช้คำสั่งค้นข้อมูล
ฟังก์ชัน sqlx::query ใช้เรียก SQL โดยตรง ผูกพารามิเตอร์ด้วย .bind(value) เพื่อป้องกันการแทรก SQL โดย Postgres ใช้ตัวยึดตำแหน่ง $1, $2 ใช้ .execute(&pool) สำหรับการเขียนข้อมูลที่ไม่ส่งคืนแถว
use sqlx::PgPool;
async fn insert_todo(pool: &PgPool, title: &str) -> Result<(), sqlx::Error> {
sqlx::query("INSERT INTO todos (title, done) VALUES ($1, $2)")
.bind(title)
.bind(false)
.execute(pool)
.await?;
Ok(())
}การจับคู่แถวกับโครงสร้าง
สร้าง sqlx::FromRow ให้กับแบบจำลอง เพื่อให้ผลลัพธ์จากคำสั่งค้นหาจับคู่เข้ากับแบบจำลองได้โดยตรง ใช้ query_as::<_, Todo> ร่วมกับ fetch_all เพื่อรับ Vec<Todo> หรือใช้ fetch_one สำหรับแถวเดียว
use sqlx::{PgPool, FromRow};
#[derive(FromRow, serde::Serialize)]
struct Todo { id: i32, title: String, done: bool }
async fn all_todos(pool: &PgPool) -> Result<Vec<Todo>, sqlx::Error> {
let rows = sqlx::query_as::<_, Todo>("SELECT id, title, done FROM todos")
.fetch_all(pool)
.await?;
Ok(rows)
}ตัวจัดการที่อ่านฐานข้อมูล
รวมส่วนต่าง ๆ เข้าด้วยกัน: ตัวจัดการรับกลุ่มการเชื่อมต่อจากสถานะ เรียกใช้คำสั่งค้นหา และส่งคืน JSON จับคู่ข้อผิดพลาดของฐานข้อมูลกับสถานะ 500 เพื่อให้ไคลเอนต์ได้รับการตอบกลับที่เรียบร้อยแทนที่จะเกิดการหยุดทำงาน
use axum::{extract::State, Json, http::StatusCode};
use sqlx::PgPool;
async fn list_todos(
State(pool): State<PgPool>,
) -> Result<Json<Vec<Todo>>, StatusCode> {
match all_todos(&pool).await {
Ok(todos) => Ok(Json(todos)),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
}
}การส่งคืนแถวที่เพิ่มเข้าไป
Postgres สามารถส่งคืนแถวที่เพิ่งเพิ่มด้วย RETURNING ใช้ร่วมกับ query_as และ fetch_one เพื่อรับระเบียนใหม่ รวมถึง id ที่สร้างขึ้น ในการเดินทางไปกลับเพียงครั้งเดียว
use sqlx::PgPool;
async fn create(pool: &PgPool, title: &str) -> Result<Todo, sqlx::Error> {
let todo = sqlx::query_as::<_, Todo>(
"INSERT INTO todos (title, done) VALUES ($1, false) \
RETURNING id, title, done")
.bind(title)
.fetch_one(pool)
.await?;
Ok(todo)
}การย้ายโครงสร้างฐานข้อมูล
โครงสร้างฐานข้อมูลต้องมีอยู่ก่อนจึงจะเรียกใช้คำสั่งค้นหาได้ sqlx รองรับการย้ายโครงสร้างฐานข้อมูล: ไฟล์ SQL ในโฟลเดอร์ migrations/ ที่นำไปใช้ตามลำดับ เรียกใช้ตอนเริ่มต้นด้วย sqlx::migrate!() เพื่อให้ฐานข้อมูลใหม่ถูกตั้งค่าโดยอัตโนมัติ
use sqlx::PgPool;
async fn run_migrations(pool: &PgPool) {
sqlx::migrate!("./migrations")
.run(pool)
.await
.expect("migrations failed");
}
// migrations/0001_init.sql contains the CREATE TABLE statements.ธุรกรรม
เมื่อการเขียนข้อมูลหลายรายการต้องสำเร็จไปพร้อมกัน ให้ห่อการทำงานเหล่านั้นไว้ในธุรกรรม เริ่มด้วย pool.begin() เรียกใช้คำสั่งค้นหากับตัวจัดการธุรกรรม แล้วจึง commit หากทิ้งธุรกรรมโดยไม่ยืนยัน sqlx จะย้อนกลับการเปลี่ยนแปลงให้อัตโนมัติ ทำให้ข้อมูลยังคงสอดคล้องกัน
use sqlx::PgPool;
async fn transfer(pool: &PgPool) -> Result<(), sqlx::Error> {
let mut tx = pool.begin().await?;
sqlx::query("UPDATE accounts SET balance = balance - 10 WHERE id = 1")
.execute(&mut *tx).await?;
sqlx::query("UPDATE accounts SET balance = balance + 10 WHERE id = 2")
.execute(&mut *tx).await?;
tx.commit().await?;
Ok(())
}การกำหนดค่าและข้อมูลลับ
อย่าฝังข้อมูลประจำตัวของฐานข้อมูลไว้ในโค้ด อ่าน DATABASE_URL จากสภาพแวดล้อม ซึ่งมักโหลดจากไฟล์ .env ด้วยเครต dotenvy ระหว่างการพัฒนา ในระบบจริง แพลตฟอร์มจะใส่ค่าให้เป็นตัวแปรสภาพแวดล้อม
use std::env;
async fn connect_from_env() -> sqlx::PgPool {
let url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
make_pool(&url).await
}ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจเกี่ยวกับการเชื่อมต่อฐานข้อมูล
สรุปทบทวน
คุณได้เชื่อมต่อฐานข้อมูลแล้ว:
- ใช้พูลการเชื่อมต่อ
PgPoolและแชร์พูลผ่าน.with_state - ใช้
query/executeสำหรับการเขียนข้อมูล และใช้query_asร่วมกับFromRowสำหรับการอ่านข้อมูลแบบมีชนิด - ผูกพารามิเตอร์ทุกครั้งเพื่อป้องกันการแทรกคำสั่ง SQL
RETURNINGใช้ดึงแถวที่เพิ่มเข้ามา ส่วนธุรกรรมใช้จัดกลุ่มการเขียนข้อมูลให้ดำเนินการแบบเป็นอะตอมิก- เรียกใช้การย้ายโครงสร้างเมื่อเริ่มต้นโปรแกรม และอ่านค่า
DATABASE_URLจากสภาพแวดล้อม
คำถามที่พบบ่อย
บทเรียน “การผสานรวมฐานข้อมูล” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การผสานรวมฐานข้อมูล” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Learn Rust Coding ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การผสานรวมฐานข้อมูล”
เก็บข้อมูลให้คงอยู่ คุณปฏิบัติ Learn Rust Coding ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Learn Rust Coding หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Learn Rust Coding บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การผสานรวมฐานข้อมูล” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Learn Rust Coding นี้ได้ไหม
ได้ บทเรียน Learn Rust Coding ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การตั้งค่าโครงการ
- ปลายทางและแบบจำลอง
- การผสานรวมฐานข้อมูล
- การทดสอบ API