Integrazione con il database
Persistenza dei dati
Integrazione con il database è una lezione Learn Rust Coding gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Learn Rust Coding, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Learn Rust Coding include 4 lezioni in totale.
Persistenza dei dati
Finora la nostra API memorizzava i dati in memoria, che vengono persi al riavvio. I servizi reali hanno bisogno di un database. In questa lezione collegheremo un'API Axum a PostgreSQL usando sqlx, un toolkit SQL asincrono per Rust con controlli in fase di compilazione.
Imparerete a usare i pool di connessioni e le query, a mappare le righe nelle struct e a usare il pool come stato condiviso.
Aggiungere sqlx
Aggiungete sqlx con le funzionalità necessarie: un runtime, TLS e un driver per il database. Di seguito è riportata una configurazione per Postgres che usa Tokio.
// Cargo.toml
// [dependencies]
// sqlx = { version = "0.7", features = [
// "runtime-tokio", "tls-rustls", "postgres", "macros"
// ] }Il pool di connessioni
Aprire una nuova connessione per ogni richiesta è lento. Un pool di connessioni mantiene un insieme di connessioni riutilizzabili. PgPoolOptions ne crea uno a partire da un URL del database. Il pool può essere copiato a basso costo (internamente usa il conteggio dei riferimenti), quindi è ideale come stato condiviso.
use sqlx::postgres::PgPoolOptions;
async fn make_pool(url: &str) -> sqlx::PgPool {
PgPoolOptions::new()
.max_connections(5)
.connect(url)
.await
.expect("failed to connect")
}Il pool come stato dell'applicazione
Passate il pool ad Axum con .with_state(pool). Gli handler accettano quindi State(pool): State<PgPool>. Poiché copiare il pool ha un costo ridotto, ogni richiesta condivide le stesse connessioni sottostanti.
use axum::{routing::get, Router};
use sqlx::PgPool;
fn build(pool: PgPool) -> Router {
Router::new()
.route("/todos", get(list_todos))
.with_state(pool)
}Eseguire una query
La funzione sqlx::query esegue SQL grezzo. Associate i parametri con .bind(value) per evitare l'SQL injection; Postgres usa i segnaposto $1, $2. Usate .execute(&pool) per le operazioni di scrittura che non restituiscono righe.
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(())
}Mappare le righe nelle struct
Derivate sqlx::FromRow sul vostro modello, così i risultati delle query vengono mappati direttamente al suo interno. Usate query_as::<_, Todo> con fetch_all per ottenere un Vec<Todo>, oppure fetch_one per una singola riga.
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)
}Un handler che legge dal database
Unite i vari elementi: un handler recupera il pool dallo stato, esegue una query e restituisce JSON. Convertite gli errori del database in uno stato 500, così il client riceve una risposta chiara invece di un panic.
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),
}
}Restituire le righe inserite
Postgres può restituire la riga appena inserita con RETURNING. Combinatelo con query_as e fetch_one per ottenere il nuovo record, compreso l'id generato, con un unico round trip.
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)
}Migrazioni
Lo schema deve esistere prima di eseguire le query. sqlx supporta le migrazioni: file SQL in una cartella migrations/ applicati in ordine. Eseguitele all'avvio con sqlx::migrate!(), così un database vuoto viene configurato automaticamente.
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.Transazioni
Quando più operazioni di scrittura devono riuscire insieme, racchiudetele in una transazione. Iniziate con pool.begin(), eseguite le query sull'handle della transazione, quindi chiamate commit. Se la transazione viene eliminata senza eseguire il commit, sqlx esegue automaticamente il rollback, mantenendo coerenti i dati.
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(())
}Configurazione e segreti
Non inserite mai nel codice le credenziali del database. Leggete DATABASE_URL dall'ambiente, spesso caricato durante lo sviluppo da un file .env con il crate dotenvy. In produzione, la piattaforma lo fornisce come variabile d'ambiente.
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
}Verifica rapida
Verificate la vostra comprensione dell'integrazione con il database.
Riepilogo
Avete integrato un database:
- Usate un pool di connessioni
PgPoole condividetelo tramite.with_state. - Usate
query/executeper le scritture;query_asconFromRowper le letture tipizzate. - Associate sempre i parametri per prevenire l'SQL injection.
RETURNINGrecupera le righe inserite; le transazioni raggruppano atomicamente le operazioni di scrittura.- Eseguite le migrazioni all'avvio e leggete
DATABASE_URLdall'ambiente.
Domande Frequenti
La lezione «Integrazione con il database» è gratuita?
Sì — il testo completo di «Integrazione con il database» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Learn Rust Coding, passa a CoddyKit PRO. Il corso Learn Rust Coding include 4 lezioni in totale.
Cosa imparerò in «Integrazione con il database»?
Persistenza dei dati Eserciti Learn Rust Coding con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Learn Rust Coding?
Non è richiesta alcuna esperienza precedente. Learn Rust Coding su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Integrazione con il database»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Learn Rust Coding?
Sì. Ogni lezione Learn Rust Coding include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Configurazione del progetto
- Endpoint e modelli
- Integrazione con il database
- Test dell'API