Verwenden Sie SQLX mit go-mssqldb

sqlx ist eine leichte Bibliothek, die Gos database/sql mit Struct-Scanning, benannter Parameterbindung und Abfragehilfen erweitert. Es funktioniert mit jedem database/sql Treiber, einschließlich go-mssqldb.

Im Gegensatz zu einem ORM generiert SQLX kein SQL und verwaltet keine Schemata. Du schreibst die Abfragen und sqlx übernimmt das Mapping zwischen Ergebnismengen und Go-Structs. Diese Ausgewogenheit macht es zu einer guten Wahl, wenn man weniger Boilerplate-Code als bei reinem database/sql möchte, jedoch ohne die Abstraktion eines vollständigen ORMs.

Beispiele in diesem Artikel laufen gegen die AdventureWorks2025-Beispieldatenbank .

SQLX installieren

Erstellen oder öffnen Sie ein Go-Modul, und fügen Sie dann sqlx und go-mssqldb hinzu:

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

Verbindung mit SQL Server herstellen

Öffnen Sie eine Verbindung unter Verwendung von sqlx.Open mit dem Treibernamen 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 gibt ein *sqlx.DB zurück, der den Standard *sql.DBumwickelt. Alle database/sql Methoden funktionieren weiterhin, plus die in diesem Artikel beschriebenen SQLX-Erweiterungen.

Struct-Scanning mit Select und Get

Der Hauptvorteil von SQLX gegenüber RAW database/sql ist das automatische Strukturscannen. Spalten mithilfe von db-Tags Strukturfeldern zuordnen:

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

Wählen Sie mehrere Zeilen aus

Select scannt alle Ergebniszeilen in einen Strukturen-Slice:

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 führt die Abfrage aus, scannt jede Zeile in eine Struktur und schließt den Zeilen-Iterator. Es ersetzt das MusterQuery / for rows.Next() / rows.Scan().

Eine einzelne Zeile abrufen

Get scannt die erste Reihe in eine einzige Struktur:

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 gibt zurück, sql.ErrNoRows wenn die Abfrage keine Zeilen liefert. Wenn mehrere Zeilen übereinstimmen, wird nur die erste gescannt.

Benannte Parameter

sqlx unterstützt benannte Parameter im Stil von :name, wobei Maps oder Structs als Parameterquelle verwendet werden. go-mssqldb verwendet jedoch @name-Parameter nativ, daher verwenden Sie für SQL Server sql.Named mit den Standardplatzhaltern @p1, wie in den vorherigen Beispielen gezeigt.

Wenn du sqlx’ :name-Syntax bevorzugst, verwende sqlx.Named, um sie neu zu binden:

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)

Note

sqlx.Named wandelt :name Platzhalter in Positionsparameter ? um. Rebind Dann konvertiert er ? in das Format des Zieltreibers. Verwenden Sie diesen zweistufigen Ansatz, wenn Sie portable, benannte Parameter über Treiber hinweg möchten.

Erweiterung der IN-Klausel

Das Erstellen WHERE column IN (...) von Klauseln mit variabler Anzahl von Werten ist mit rohem SQL mühsam. Verwenden Sie sqlx.In, um ein Array in einzelne Platzhalter aufzuteilen:

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 erweitert den einzelnen ? zu ?, ?, ? und fügt den Slice in die Argumentliste ein. Rebind wird dann in das Platzhalter-Format des Treibers umgewandelt.

Schreibvorgänge ausführen

Verwenden Sie Exec für die INSERT-, UPDATE-Anweisung und die DELETE-Anweisung. Diese Beispiele sind auf Production.ScrapReason ausgerichtet, sodass sie keine kritischen Tabellen verändern.

Achtung

SQLX bietet außerdem MustExec, das einen Panik-Fehler auslöst, anstatt einen Fehler zurückzugeben. Vermeiden Sie MustExec in Produktionsdiensten, denn ein nicht behobener Panikfehler beendet den Prozess. Die Beispiele in diesem Abschnitt verwenden Exec mit expliziten Fehlerprüfungen.

Fügen Sie eine Reihe ein

Verwenden Sie Exec mit einer INSERTAnweisung und benannten Parametern:

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"))

Transaktion mit SQLX

Vorgänge mit Beginx in einer Transaktion verpacken:

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"))

Der Aufruf defer tx.Rollback() stellt sicher, dass die Transaktion beseitigt wird, falls eine Operation fehlschlägt. Nach einem erfolgreichen Commit ist Rollback eine sichere Nulloperation. Das Objekt tx stellt dieselben sqlx-Helfer (Select, Get, Exec) bereit, die auf die Transaktion beschränkt sind.

Scannen von Strukturen mit Joins

SQLX kann Spalten aus verbundenen Abfragen in eine flache Struktur oder eine eingebettete Struktur scannen.

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)
}

Wann man sich für sqlx vs. GORM vs. database/sql entscheidet

Wählen... Wenn es am besten passt
database/sql Du willst keine Abhängigkeiten über den Treiber hinaus oder du brauchst die volle Kontrolle über jede Abfrage.
sqlx Du möchtest das Scannen von Strukturen und Hilfsfunktionen für Abfragen ohne die Abstraktionsschicht eines ORM.
GORM Du willst Schema-Management, Assoziationen, Hooks und ein vollständiges ORM-Feature-Set.