Porównanie ORM dla PostgreSQL w języku Go: GORM vs Ent vs Bun vs sqlc
Praktyczny, kodowy przegląd ORM w GO ```
Page content
Najbardziej znane ORMy dla GO to GORM, Ent, Bun i sqlc. Oto mała porównanie ich z przykładami operacji CRUD w czystym GO.
TL;DR
- GORM: bogaty w funkcje i wygodny; najłatwiejszy do „wysłania”, ale ma większy narzut czasu wykonywania.
- Ent: schemat jako kod z wygenerowanymi, typowo bezpiecznymi API; doskonały do dużych baz kodu i refaktoryzacji.
- Bun: lekki, budowniczy zapytań/ORM oparty na SQL; szybki z dobrymi funkcjami Postgres, jasny przez projekt.
- sqlc (nie jest ORM, ale nadal): pisz SQL, otrzymuj typowo bezpieczny Go; najlepsza surowa wydajność i kontrola, brak magii czasu wykonywania.
Kryteria wyboru i szybkie porównanie
Moje kryteria to:
- Wydajność: opóźnienie/przepustowość, unikalny narzut, operacje wsadowe.
- DX: krzywa uczenia, bezpieczeństwo typów, debugowalność, tarcia związane z generowaniem kodu.
- Ecosystem: dokumentacja, przykłady, aktywność, integracje (migracje, śledzenie).
- Zestaw funkcji: relacje, ładowanie wsteczne, migracje, hooki, ujawnione wyloty SQL.
Narzędzie | Paradygmat | Bezpieczeństwo typów | Relacje | Migracje | Ergonomia SQL surowego | Typowy przypadek użycia |
---|---|---|---|---|---|---|
GORM | ORM stylu Active Record | Średnie (czasu wykonywania) | Tak (tagi, Preload/Joins) | Auto-migracja (opcjonalna) | db.Raw(...) |
Szybkie dostarczanie, bogate funkcje, konwencjonalne aplikacje CRUD |
Ent | Schemat → generowanie kodu → płynny API | Wysokie (czasu kompilacji) | Pierwszoplanowe (krawędzie) | Wygenerowany SQL (oddzielny krok) | entsql , niestandardowy SQL |
Duże bazy kodu, zespoły intensywnie refaktoryzujące, ścisłe typowanie |
Bun | Budowniczy zapytań/ORM oparty na SQL | Średnie–Wysokie | Jawne (Relation ) |
Oddzielny pakiet migracji | Naturalny (budowniczy + surowy) | Usługi świadczące z uwzględnieniem wydajności, funkcje Postgres |
sqlc | SQL → generowane funkcje (nie jest ORM) | Wysokie (czasu kompilacji) | Poprzez łączenia SQL | Narzędzie zewnętrzne (np. golang-migrate) | To jest SQL | Maksymalna kontrola i szybkość; zespoły przyjazne dla administratorów baz danych |
Przykład operacji CRUD
Konfiguracja (PostgreSQL)
Użyj pgx lub nativego sterownika PG narzędzia. Przykład DSN:
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
Importy (wspólne dla wszystkich ORM)
Na początku każdego pliku z go code przykład dodaj:
import (
"context"
"os"
)
Zamodelujemy prostą tabelę users
:
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
GORM
Inicjalizacja
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type User struct {
ID int64 `gorm:"primaryKey"`
Name string
Email string `gorm:"uniqueIndex"`
}
func newGorm() (*gorm.DB, error) {
dsn := os.Getenv("DATABASE_URL")
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}
// Auto-migracja (opcjonalna; ostrożnie w produkcji)
func migrate(db *gorm.DB) error { return db.AutoMigrate(&User{}) }
CRUD
func gormCRUD(ctx context.Context, db *gorm.DB) error {
// Tworzenie
u := User{Name: "Alice", Email: "alice@example.com"}
if err := db.WithContext(ctx).Create(&u).Error; err != nil { return err }
// Odczyt
var got User
if err := db.WithContext(ctx).First(&got, u.ID).Error; err != nil { return err }
// Aktualizacja
if err := db.WithContext(ctx).Model(&got).
Update("email", "alice+1@example.com").Error; err != nil { return err }
// Usunięcie
if err := db.WithContext(ctx).Delete(&User{}, got.ID).Error; err != nil { return err }
return nil
}
Uwagi
- Relacje przez tagi struktury +
Preload
/Joins
. - Pomocnik transakcji:
db.Transaction(func(tx *gorm.DB) error { ... })
.
Ent
Definicja schematu (w ent/schema/user.go
):
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
type User struct {
ent.Schema
}
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int64("id").Unique().Immutable(),
field.String("name"),
field.String("email").Unique(),
}
}
Generowanie kodu
go run entgo.io/ent/cmd/ent generate ./ent/schema
Inicjalizacja
import (
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"your/module/ent"
)
func newEnt() (*ent.Client, error) {
dsn := os.Getenv("DATABASE_URL")
drv, err := sql.Open(dialect.Postgres, dsn)
if err != nil { return nil, err }
return ent.NewClient(ent.Driver(drv)), nil
}
CRUD
func entCRUD(ctx context.Context, client *ent.Client) error {
// Tworzenie
u, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
if err != nil { return err }
// Odczyt
got, err := client.User.Get(ctx, u.ID)
if err != nil { return err }
// Aktualizacja
if _, err := client.User.UpdateOneID(got.ID).
SetEmail("alice+1@example.com").
Save(ctx); err != nil { return err }
// Usunięcie
if err := client.User.DeleteOneID(got.ID).Exec(ctx); err != nil { return err }
return nil
}
Uwagi
- Silne typowanie end-to-end; krawędzie dla relacji.
- Wygenerowane migracje lub użyj swojego narzędzia migracji.
Bun
Inicjalizacja
import (
"database/sql"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
_ "github.com/jackc/pgx/v5/stdlib"
)
type User struct {
bun.BaseModel `bun:"table:users"`
ID int64 `bun:",pk,autoincrement"`
Name string `bun:",notnull"`
Email string `bun:",unique,notnull"`
}
func newBun() (*bun.DB, error) {
dsn := os.Getenv("DATABASE_URL")
sqldb, err := sql.Open("pgx", dsn)
if err != nil { return nil, err }
return bun.NewDB(sqldb, pgdialect.New()), nil
}
CRUD
func bunCRUD(ctx context.Context, db *bun.DB) error {
// Tworzenie
u := &User{Name: "Alice", Email: "alice@example.com"}
if _, err := db.NewInsert().Model(u).Exec(ctx); err != nil { return err }
// Odczyt
var got User
if err := db.NewSelect().Model(&got).
Where("id = ?", u.ID).
Scan(ctx); err != nil { return err }
// Aktualizacja
if _, err := db.NewUpdate().Model(&got).
Set("email = ?", "alice+1@example.com").
WherePK().
Exec(ctx); err != nil { return err }
// Usunięcie
if _, err := db.NewDelete().Model(&got).WherePK().Exec(ctx); err != nil { return err }
return nil
}
Uwagi
- Jawne łączenia/ładowanie wsteczne z
.Relation("...")
. - Oddzielny pakiet
bun/migrate
dla migracji.
sqlc
sqlc technicznie nie jest ORM. Pisz SQL; generuje typowo bezpieczne metody Go.
sqlc.yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: internal/db
sql_package: "database/sql" # lub "github.com/jackc/pgx/v5"
Zapytania (db/queries/users.sql
)
-- name: CreateUser :one
INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING id, name, email;
-- name: GetUser :one
SELECT id, name, email FROM users WHERE id = $1;
-- name: UpdateUserEmail :one
UPDATE users SET email = $2 WHERE id = $1
RETURNING id, name, email;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;
Generowanie
sqlc generate
Użycie
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"your/module/internal/db"
)
func sqlcCRUD(ctx context.Context) error {
dsn := os.Getenv("DATABASE_URL")
sqldb, err := sql.Open("pgx", dsn)
if err != nil { return err }
q := db.New(sqldb)
// Tworzenie
u, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "Alice", Email: "alice@example.com",
})
if err != nil { return err }
// Odczyt
got, err := q.GetUser(ctx, u.ID)
if err != nil { return err }
// Aktualizacja
up, err := q.UpdateUserEmail(ctx, db.UpdateUserEmailParams{
ID: got.ID, Email: "alice+1@example.com",
})
if err != nil { return err }
// Usunięcie
if err := q.DeleteUser(ctx, up.ID); err != nil { return err }
return nil
}
Uwagi
- Przygotuj własne migracje (np.
golang-migrate
). - Dla dynamicznych zapytań: napisz wiele wariantów SQL lub połącz z małym budownikiem.
Uwagi dotyczące wydajności
- GORM: wygodny, ale dodaje narzut refleksji/abstrakcji. Dobre dla typowych CRUD; uważaj na zapytania N+1 (preferuj
Joins
lub selektywnePreload
). - Ent: generowany kod unika refleksji; dobre dla złożonych schematów. Często szybsze niż ciężkie ORMs z magią czasu wykonywania.
- Bun: cienki nad
database/sql
; szybki, jawny, świetny do operacji wsadowych i dużych zestawów wyników. - sqlc: praktycznie wydajność surowego SQL z bezpieczeństwem czasu kompilacji.
Ogólne wskazówki
- Używaj pgx jako sterownika (w5) i context wszędzie.
- Preferuj operacje wsadowe (
COPY
, wielowierszoweINSERT
) dla wysokiej przepustowości. - Profiluj SQL:
EXPLAIN ANALYZE
, indeksy, indeksy pokrywające, unikaj zbędnych podróży. - Odnawiaj połączenia; dostosuj rozmiar puli na podstawie obciążenia.
Doświadczenie dewelopera i ekosystem
- GORM: największa społeczność, wiele przykładów/wtyczek; steeper learning curve dla zaawansowanych wzorców.
- Ent: świetne dokumenty; krok generowania kodu jest głównym zmianą modelu myślenia; bardzo przyjazne dla refaktoryzacji.
- Bun: czytelne, przewidywalne zapytania; mniejsza, ale aktywna społeczność; świetne funkcje Postgres.
- sqlc: minimalne zależności czasu wykonywania; dobrze integruje się z narzędziami migracji i CI; doskonałe dla zespołów zgodnych z SQL.
Wyróżnione funkcje
- Relacje i ładowanie wsteczne: wszystkie obsługują relacje; GORM (tagi +
Preload
/Joins
), Ent (krawędzie +.With...()
), Bun (Relation(...)
), sqlc (pisz łączenia SQL). - Migracje: GORM (auto-migracja; ostrożnie w produkcji), Ent (wygenerowany SQL), Bun (
bun/migrate
), sqlc (narzędzia zewnętrzne). - Hooki/rozszerzalność: GORM (callbacki/wtyczki), Ent (hooki/middleware + szablony/generowanie kodu), Bun (hooki podobne do middleware, łatwe SQL surowe), sqlc (składaj w warstwie aplikacji).
- JSON/Tablice (Postgres): Bun i GORM mają pomocniki; Ent/sqlc obsługują poprzez typy niestandardowe lub SQL.
Kiedy wybrać co
- Wybierz GORM, jeśli chcesz maksymalną wygodę, bogate funkcje i szybkie prototypowanie dla konwencjonalnych usług CRUD.
- Wybierz Ent, jeśli wartość bezpieczeństwa czasu kompilacji, jawnych schematów i długofalowej utrzywalności w większych zespołach.
- Wybierz Bun, jeśli chcesz wydajność i jawne zapytania SQL z komfortem ORM tam, gdzie pomaga.
- Wybierz sqlc, jeśli (i twój zespół) preferuje czysty SQL z typowo bezpiecznymi wiązaniami Go i zero narzutu czasu wykonywania.
Minimalny docker-compose.yml
dla lokalnego PostgreSQL
version: "3.8"
services:
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports: ["5432:5432"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 5s
timeout: 3s
retries: 5
Pakiety i biblioteki ORM w GO
Inne przydatne linki
- Golang Cheat Sheet
- Wykonanie AWS lambda: JavaScript vs Python vs Golang
- Poprawianie błędu Golang GORM AutoMigrate PostgreSQL
- Przeporządkowanie dokumentów tekstowych z użyciem Ollama i modelu Qwen3 Embedding - w Go
- Przeporządkowanie dokumentów tekstowych z użyciem Ollama i modelu Qwen3 Reranker - w Go