Użyj sqlx z go-mssqldb

sqlx to lekka biblioteka, która rozszerza Go database/sql o skanowanie struktur, wiązanie parametrów o nazwy oraz pomocników zapytań. Działa z dowolnym sterownikiem database/sql, w tym go-mssqldb.

W przeciwieństwie do ORM, sqlx nie generuje SQL ani nie zarządza schematami. Ty piszesz zapytania, a sqlx obsługuje mapowanie między zestawami wyników a strukturami Go. Ta równowaga sprawia, że jest to dobry wybór, gdy chcesz pisać mniej powtarzalnego kodu niż przy użyciu samego database/sql, ale bez abstrakcji pełnego ORM.

Przykłady w tym artykule porównane są z przykładową bazą danych AdventureWorks2025 .

Instaluj sqlx

Stwórz lub otwórz moduł Go, a następnie dodaj sqlx i :go-mssqldb

mkdir go-sqlx-sqlserver
cd go-sqlx-sqlserver
go mod init example.com/go-sqlx-sqlserver
go get github.com/jmoiron/sqlx
go get github.com/microsoft/go-mssqldb

Nawiązywanie połączenia z programem SQL Server

Otwórz połączenie za pomocą sqlx.Open o nazwie sterownika sqlserver:

package main

import (
    "fmt"
    "log"

    "github.com/jmoiron/sqlx"
    _ "github.com/microsoft/go-mssqldb"
)

func main() {
    // Replace <server>, <user>, and <password> with your own values.
    // In production, load credentials from environment variables or a secret store.
    dsn := "sqlserver://<user>:<password>@<server>.database.windows.net:1433?database=AdventureWorks2025&encrypt=true&TrustServerCertificate=false"
    db, err := sqlx.Open("sqlserver", dsn)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    if err := db.Ping(); err != nil {
        log.Fatal(err)
    }
    fmt.Println("Connected to SQL Server with sqlx")
}

sqlx.Open zwraca obiekt *sqlx.DB, który opakowuje standardowy *sql.DB. Wszystkie database/sql metody nadal działają, plus rozszerzenia sqlx opisane w tym artykule.

Skanowanie struktury za pomocą Select i Get

Główną zaletą sqlx w porównaniu do raw database/sql jest automatyczne skanowanie struktur. Przypisz kolumny do pól struktury za pomocą znaczników db:

type Product struct {
    ProductID int32   `db:"ProductID"`
    Name      string  `db:"Name"`
    ListPrice float64 `db:"ListPrice"`
}

Wybierz wiele wierszy

Select skanuje wszystkie wiersze wynikowe do wycinka struktur:

var products []Product
err := db.Select(&products,
    "SELECT TOP 5 ProductID, Name, ListPrice FROM Production.Product WHERE ListPrice > @p1 ORDER BY ListPrice DESC",
    sql.Named("p1", 1000))
if err != nil {
    log.Fatal(err)
}
for _, p := range products {
    fmt.Printf("%-35s $%.2f\n", p.Name, p.ListPrice)
}

Select wykonuje zapytanie, skanuje każdy wiersz w strukturę i zamyka iterator wiersza. Zastępuje Query / for rows.Next() / rows.Scan() wzór.

Pobierz pojedynczy wiersz

Get skanuje pierwszy wiersz do pojedynczej struktury:

var product Product
err := db.Get(&product,
    "SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID = @p1",
    sql.Named("p1", 680))
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%s: $%.2f\n", product.Name, product.ListPrice)

Get zwraca, sql.ErrNoRows jeśli zapytanie nie zwraca żadnych wierszy. Jeśli wiele wierszy się zgadza, skanowany jest tylko pierwszy.

Nazwane parametry

SQLX obsługuje :name parametry nazwane stylem, wykorzystując mapy lub struktury jako źródło parametrów. Jednak go-mssqldb natywnie używa parametrów @name, więc w przypadku SQL Server użyj sql.Named ze standardowymi symbolami zastępczymi @p1, jak pokazano w poprzednich przykładach.

Jeśli wolisz składnię :name biblioteki sqlx, użyj sqlx.Named, aby ponownie powiązać:

query, args, err := sqlx.Named(
    "SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID = :id",
    map[string]interface{}{"id": 680})
if err != nil {
    log.Fatal(err)
}
// Rebind to positional placeholders for go-mssqldb
query = db.Rebind(query)

var product Product
err = db.Get(&product, query, args...)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%s: $%.2f\n", product.Name, product.ListPrice)

Uwaga / Notatka

sqlx.Named konwertuje symbole zastępcze :name na parametry pozycyjne ?. Rebind następnie konwertuje do ? formatu docelowego sterownika. Stosuj to dwustopniowe podejście, gdy chcesz zapewnić przenośną obsługę nazwanych parametrów w różnych sterownikach.

Rozwinięcie klauzuli IN

Budowanie WHERE column IN (...) klauzul o zmiennej liczbie wartości jest żmudne przy czystym SQL. Użyj sqlx.In, aby rozwinąć wycinek do postaci pojedynczych symboli zastępczych:

ids := []int32{680, 706, 707}
query, args, err := sqlx.In(
    "SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID IN (?)", ids)
if err != nil {
    log.Fatal(err)
}
query = db.Rebind(query)

var products []Product
err = db.Select(&products, query, args...)
if err != nil {
    log.Fatal(err)
}
for _, p := range products {
    fmt.Printf("%d: %s ($%.2f)\n", p.ProductID, p.Name, p.ListPrice)
}

sqlx.In Rozszerza pojedynczy ? fragment do ?, ?, ? listy ARGS i spłaszcza wycinek. Rebind następnie konwertuje do formatu symbolu zastępczego sterownika.

Wykonywanie zapisów

Zastosowanie Exec dla INSERT, UPDATE, i DELETE zdań. Te przykłady dotyczą Production.ScrapReason, więc nie modyfikują kluczowych tabel.

Uwaga

sqlx oferuje również MustExec, które zamiast zwracać błąd powoduje panikę. Unikaj używania MustExec w usługach produkcyjnych, ponieważ panic, po której nie następuje odzyskanie, powoduje zakończenie procesu. W przykładach w tej sekcji użyto Exec z jawnym sprawdzaniem błędów.

Wstaw wiersz

Użyj Exec z instrukcją INSERT i nazwanymi parametrami:

result, err := db.Exec(
    "INSERT INTO Production.ScrapReason (Name, ModifiedDate) VALUES (@p1, @p2)",
    sql.Named("p1", "sqlx test scrap"), sql.Named("p2", time.Now()))
if err != nil {
    log.Fatal(err)
}
affected, _ := result.RowsAffected()
fmt.Printf("Inserted %d row(s)\n", affected)

// Clean up
db.Exec("DELETE FROM Production.ScrapReason WHERE Name = @p1",
    sql.Named("p1", "sqlx test scrap"))

Transakcja z sqlx

Opakuj operacje w transakcję, używając Beginx:

tx, err := db.Beginx()
if err != nil {
    log.Fatal(err)
}
defer tx.Rollback() // no-op after Commit, but essential if an error occurs

if _, err := tx.Exec(
    "INSERT INTO Production.ScrapReason (Name, ModifiedDate) VALUES (@p1, @p2)",
    sql.Named("p1", "sqlx tx scrap 1"), sql.Named("p2", time.Now())); err != nil {
    log.Fatal(err)
}
if _, err := tx.Exec(
    "INSERT INTO Production.ScrapReason (Name, ModifiedDate) VALUES (@p1, @p2)",
    sql.Named("p1", "sqlx tx scrap 2"), sql.Named("p2", time.Now())); err != nil {
    log.Fatal(err)
}

if err := tx.Commit(); err != nil {
    log.Fatal(err)
}
fmt.Println("Transaction committed")

// Clean up
db.Exec("DELETE FROM Production.ScrapReason WHERE Name IN (@p1, @p2)",
    sql.Named("p1", "sqlx tx scrap 1"), sql.Named("p2", "sqlx tx scrap 2"))

Wywołanie defer tx.Rollback() zapewnia wycofanie transakcji w przypadku niepowodzenia dowolnej operacji. Po udanym Commit, Rollback jest bezpieczny no-op. Obiekt tx udostępnia te same funkcje pomocnicze sqlx (Select, Get, Exec) ograniczone do transakcji.

Skanowanie struktur ze złączeniami

sqlx może skanować kolumny z połączonych zapytań w strukturę płaską lub osadzoną.

type ProductWithCategory struct {
    ProductID    int32   `db:"ProductID"`
    ProductName  string  `db:"ProductName"`
    CategoryName string  `db:"CategoryName"`
    ListPrice    float64 `db:"ListPrice"`
}

var results []ProductWithCategory
err := db.Select(&results, `
    SELECT TOP 5
        p.ProductID,
        p.Name AS ProductName,
        pc.Name AS CategoryName,
        p.ListPrice
    FROM Production.Product p
    JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
    JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID
    WHERE p.ListPrice > @p1
    ORDER BY p.ListPrice DESC`,
    sql.Named("p1", 1000))
if err != nil {
    log.Fatal(err)
}
for _, r := range results {
    fmt.Printf("%-35s %-15s $%.2f\n", r.ProductName, r.CategoryName, r.ListPrice)
}

Kiedy wybrać sqlx, GORM czy database/sql

Wybierz... Kiedy najlepiej pasuje
database/sql Nie chcesz żadnych zależności poza sterownikiem albo musisz mieć pełną kontrolę nad każdym zapytaniem.
sqlx Potrzebujesz skanowania struktur i narzędzi pomocniczych do zapytań bez warstwy abstrakcji ORM.
GORM Chcesz zarządzania schematami, skojarzeń, hooków i pełnego zestawu funkcji ORM.