Integracja z bazą danych
Trwałe przechowywanie danych
Integracja z bazą danych to bezpłatna lekcja Learn Rust Coding na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Learn Rust Coding, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Learn Rust Coding zawiera 4 lekcji w sumie.
Trwałe przechowywanie danych
Do tej pory nasze API przechowywało dane w pamięci, więc znikały one po ponownym uruchomieniu. Rzeczywiste usługi potrzebują bazy danych. W tej lekcji połączymy interfejs API Axum z PostgreSQL za pomocą sqlx — asynchronicznego narzędzia SQL dla Rust, które sprawdza zapytania podczas kompilacji.
Nauczysz się korzystać z pul połączeń, wykonywać zapytania, mapować wiersze na struktury oraz używać puli jako współdzielonego stanu.
Dodawanie sqlx
Dodaj sqlx z potrzebnymi funkcjami: środowiskiem uruchomieniowym, TLS i sterownikiem bazy danych. Poniżej przedstawiono konfigurację Postgresa korzystającą z Tokio.
// Cargo.toml
// [dependencies]
// sqlx = { version = "0.7", features = [
// "runtime-tokio", "tls-rustls", "postgres", "macros"
// ] }Pula połączeń
Otwieranie nowego połączenia dla każdego żądania jest powolne. Pula połączeń przechowuje zestaw połączeń wielokrotnego użytku. PgPoolOptions tworzy ją na podstawie adresu URL bazy danych. Pula jest tania do klonowania (wewnętrznie korzysta z liczenia odwołań), dzięki czemu idealnie nadaje się na współdzielony stan.
use sqlx::postgres::PgPoolOptions;
async fn make_pool(url: &str) -> sqlx::PgPool {
PgPoolOptions::new()
.max_connections(5)
.connect(url)
.await
.expect("failed to connect")
}Pula jako stan aplikacji
Przekaż pulę do Axum za pomocą .with_state(pool). Handlery przyjmują następnie State(pool): State<PgPool>. Ponieważ klonowanie puli jest tanie, każde żądanie współdzieli te same połączenia bazowe.
use axum::{routing::get, Router};
use sqlx::PgPool;
fn build(pool: PgPool) -> Router {
Router::new()
.route("/todos", get(list_todos))
.with_state(pool)
}Wykonywanie zapytania
Funkcja sqlx::query wykonuje surowy SQL. Bindowanie parametrów za pomocą .bind(value) chroni przed SQL injection; Postgres używa symboli zastępczych $1, $2. Użyj .execute(&pool) dla operacji zapisu, które nie zwracają wierszy.
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(())
}Mapowanie wierszy na struktury
Wyprowadź dla modelu implementację sqlx::FromRow, aby wyniki zapytań były bezpośrednio mapowane na ten model. Użyj query_as::<_, Todo> wraz z fetch_all, aby otrzymać Vec<Todo>, albo fetch_one dla pojedynczego wiersza.
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)
}Handler odczytujący bazę danych
Połącz wszystkie elementy: handler pobiera pulę ze stanu, wykonuje zapytanie i zwraca JSON. Mapuj błędy bazy danych na status 500, aby klient otrzymał poprawną odpowiedź zamiast paniki programu.
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),
}
}Zwracanie wstawionych wierszy
Postgres może zwrócić wiersz, który właśnie wstawił, za pomocą RETURNING. Połącz to z query_as i fetch_one, aby w jednym przebiegu pobrać nowy rekord, w tym wygenerowane 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)
}Migracje
Schemat musi istnieć przed wykonaniem zapytań. sqlx obsługuje migracje: pliki SQL w folderze migrations/ stosowane w kolejności. Uruchamiaj je podczas startu za pomocą sqlx::migrate!(), aby nowa baza danych była konfigurowana automatycznie.
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.Transakcje
Gdy kilka operacji zapisu musi zakończyć się powodzeniem razem, umieść je w transakcji. Rozpocznij ją za pomocą pool.begin(), wykonuj zapytania na uchwycie transakcji, a następnie wywołaj commit. Jeśli usuniesz uchwyt bez zatwierdzenia, sqlx automatycznie wycofa transakcję, zachowując spójność danych.
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(())
}Konfiguracja i dane uwierzytelniające
Nigdy nie umieszczaj danych uwierzytelniających do bazy na stałe w kodzie. Odczytuj DATABASE_URL ze środowiska; podczas tworzenia aplikacji jest on często ładowany z pliku .env za pomocą crate’a dotenvy. W środowisku produkcyjnym platforma wstrzykuje go jako zmienną środowiskową.
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
}Szybkie sprawdzenie
Sprawdź swoją wiedzę o integracji z bazą danych.
Podsumowanie
Zintegrował(a) Pan(i) bazę danych:
- Proszę używać puli połączeń
PgPooli udostępniać ją za pomocą.with_state. query/executesłużą do zapisów, aquery_aswraz zFromRowdo typowanych odczytów.- Proszę zawsze wiązać parametry, aby zapobiegać wstrzykiwaniu SQL.
RETURNINGpobiera wstawione wiersze, a transakcje grupują zapisy atomowo.- Proszę uruchamiać migracje podczas startu aplikacji i odczytywać
DATABASE_URLze zmiennych środowiskowych.
Często zadawane pytania
Czy lekcja „Integracja z bazą danych” jest bezpłatna?
Tak — pełny tekst „Integracja z bazą danych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Learn Rust Coding, przejdź na CoddyKit PRO. Kurs Learn Rust Coding zawiera 4 lekcji w sumie.
Co nauczysz się w „Integracja z bazą danych”?
Trwałe przechowywanie danych Ćwiczysz Learn Rust Coding z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Learn Rust Coding?
Nie wymagamy żadnego doświadczenia. Learn Rust Coding w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Integracja z bazą danych”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Learn Rust Coding?
Tak. Każda lekcja Learn Rust Coding zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Konfiguracja projektu
- Endpointy i modele
- Integracja z bazą danych
- Testowanie API