Go sqlx

SQLx 是 Go 语言的一个数据库扩展库,它在标准库 database/sql 的基础上提供了更强大的功能。SQLx 不是 ORM,而是对标准库的增强,保持了 SQL 的灵活性和性能,同时提供了更好的开发体验。

下面是一个示例:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"
    "time"

    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    _ "github.com/mattn/go-sqlite3"
)

// 1. 数据模型定义
type User struct {
    ID        int64     `db:"id" json:"id"`
    Name      string    `db:"name" json:"name"`
    Email     string    `db:"email" json:"email"`
    Age       int       `db:"age" json:"age"`
    CreatedAt time.Time `db:"created_at" json:"created_at"`
    UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

type Product struct {
    ID          int64   `db:"id" json:"id"`
    Name        string  `db:"name" json:"name"`
    Price       float64 `db:"price" json:"price"`
    Description string  `db:"description" json:"description"`
    Stock       int     `db:"stock" json:"stock"`
    IsActive    bool    `db:"is_active" json:"is_active"`
}

type Order struct {
    ID        int64     `db:"id" json:"id"`
    UserID    int64     `db:"user_id" json:"user_id"`
    Amount    float64   `db:"amount" json:"amount"`
    Status    string    `db:"status" json:"status"`
    CreatedAt time.Time `db:"created_at" json:"created_at"`
}

type OrderItem struct {
    ID        int64   `db:"id" json:"id"`
    OrderID   int64   `db:"order_id" json:"order_id"`
    ProductID int64   `db:"product_id" json:"product_id"`
    Quantity  int     `db:"quantity" json:"quantity"`
    Price     float64 `db:"price" json:"price"`
}

// 2. 数据库服务
type Database struct {
    *sqlx.DB
}

func NewDatabase(driver, dsn string) (*Database, error) {
    db, err := sqlx.Open(driver, dsn)
    if err != nil {
        return nil, fmt.Errorf("连接数据库失败: %v", err)
    }

    // 测试连接
    if err := db.Ping(); err != nil {
        return nil, fmt.Errorf("数据库连接测试失败: %v", err)
    }

    // 配置连接池
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5 * time.Minute)

    return &Database{db}, nil
}

func (d *Database) Close() error {
    return d.DB.Close()
}

func (d *Database) InitSchema() error {
    // 创建用户表
    _, err := d.Exec(`
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(255) UNIQUE,
            age INTEGER DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    `)
    if err != nil {
        return err
    }

    // 创建产品表
    _, err = d.Exec(`
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(255) NOT NULL,
            price DECIMAL(10,2) NOT NULL,
            description TEXT,
            stock INTEGER DEFAULT 0,
            is_active BOOLEAN DEFAULT true
        )
    `)
    if err != nil {
        return err
    }

    // 创建订单表
    _, err = d.Exec(`
        CREATE TABLE IF NOT EXISTS orders (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            amount DECIMAL(10,2) NOT NULL,
            status VARCHAR(50) DEFAULT 'pending',
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users (id)
        )
    `)
    if err != nil {
        return err
    }

    // 创建订单项表
    _, err = d.Exec(`
        CREATE TABLE IF NOT EXISTS order_items (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            order_id INTEGER NOT NULL,
            product_id INTEGER NOT NULL,
            quantity INTEGER DEFAULT 1,
            price DECIMAL(10,2) NOT NULL,
            FOREIGN KEY (order_id) REFERENCES orders (id),
            FOREIGN KEY (product_id) REFERENCES products (id)
        )
    `)
    if err != nil {
        return err
    }

    fmt.Println("✅ 数据库表结构初始化完成")
    return nil
}

// 3. 基本 CRUD 操作示例
func demonstrateBasicCRUD(db *Database) {
    fmt.Println("\n=== 基本 CRUD 操作 ===")

    // 创建用户
    fmt.Println("\n1. 插入数据:")
    user := User{
        Name:  "张三",
        Email: "zhang@example.com",
        Age:   25,
    }

    query := `INSERT INTO users (name, email, age) VALUES (?, ?, ?)`
    result, err := db.Exec(query, user.Name, user.Email, user.Age)
    if err != nil {
        log.Printf("插入用户失败: %v", err)
        return
    }

    userID, _ := result.LastInsertId()
    fmt.Printf("创建用户成功,ID: %d\n", userID)

    // 查询单个用户
    fmt.Println("\n2. 查询单个用户:")
    var fetchedUser User
    err = db.Get(&fetchedUser, "SELECT * FROM users WHERE id = ?", userID)
    if err != nil {
        log.Printf("查询用户失败: %v", err)
        return
    }
    fmt.Printf("查询结果: ID=%d, 姓名=%s, 邮箱=%s, 年龄=%d\n",
        fetchedUser.ID, fetchedUser.Name, fetchedUser.Email, fetchedUser.Age)

    // 查询多个用户
    fmt.Println("\n3. 查询多个用户:")
    var users []User
    err = db.Select(&users, "SELECT * FROM users ORDER BY id")
    if err != nil {
        log.Printf("查询用户列表失败: %v", err)
        return
    }
    fmt.Println("所有用户:")
    for _, u := range users {
        fmt.Printf("  - ID: %d, 姓名: %s, 邮箱: %s\n", u.ID, u.Name, u.Email)
    }

    // 更新用户
    fmt.Println("\n4. 更新数据:")
    updateQuery := `UPDATE users SET age = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`
    result, err = db.Exec(updateQuery, 26, userID)
    if err != nil {
        log.Printf("更新用户失败: %v", err)
        return
    }
    rowsAffected, _ := result.RowsAffected()
    fmt.Printf("更新成功,影响行数: %d\n", rowsAffected)

    // 删除用户
    fmt.Println("\n5. 删除数据:")
    deleteQuery := `DELETE FROM users WHERE id = ?`
    result, err = db.Exec(deleteQuery, userID)
    if err != nil {
        log.Printf("删除用户失败: %v", err)
        return
    }
    rowsAffected, _ = result.RowsAffected()
    fmt.Printf("删除成功,影响行数: %d\n", rowsAffected)
}

// 4. 命名参数示例
func demonstrateNamedParameters(db *Database) {
    fmt.Println("\n=== 命名参数示例 ===")

    // 使用命名参数插入数据
    fmt.Println("\n1. 命名参数插入:")
    product := Product{
        Name:        "MacBook Pro",
        Price:       12999.99,
        Description: "苹果笔记本电脑",
        Stock:       50,
        IsActive:    true,
    }

    query := `
        INSERT INTO products (name, price, description, stock, is_active)
        VALUES (:name, :price, :description, :stock, :is_active)
    `

    result, err := db.NamedExec(query, product)
    if err != nil {
        log.Printf("插入产品失败: %v", err)
        return
    }

    productID, _ := result.LastInsertId()
    fmt.Printf("创建产品成功,ID: %d\n", productID)

    // 使用命名参数查询
    fmt.Println("\n2. 命名参数查询:")
    var fetchedProduct Product
    err = db.Get(&fetchedProduct, "SELECT * FROM products WHERE id = :id",
        map[string]interface{}{"id": productID})
    if err != nil {
        log.Printf("查询产品失败: %v", err)
        return
    }
    fmt.Printf("查询结果: %s, 价格: ¥%.2f, 库存: %d\n",
        fetchedProduct.Name, fetchedProduct.Price, fetchedProduct.Stock)

    // 批量插入
    fmt.Println("\n3. 批量插入:")
    products := []Product{
        {Name: "iPhone 14", Price: 5999.99, Description: "苹果手机", Stock: 100},
        {Name: "iPad Air", Price: 4399.99, Description: "苹果平板", Stock: 30},
        {Name: "AirPods", Price: 1299.99, Description: "无线耳机", Stock: 200},
    }

    batchQuery := `
        INSERT INTO products (name, price, description, stock, is_active)
        VALUES (:name, :price, :description, :stock, :is_active)
    `

    _, err = db.NamedExec(batchQuery, products)
    if err != nil {
        log.Printf("批量插入失败: %v", err)
        return
    }
    fmt.Printf("批量插入了 %d 个产品\n", len(products))
}

// 5. 复杂查询示例
func demonstrateComplexQueries(db *Database) {
    fmt.Println("\n=== 复杂查询示例 ===")

    // IN 查询
    fmt.Println("\n1. IN 查询:")
    var expensiveProducts []Product
    query, args, err := sqlx.In(`
        SELECT * FROM products
        WHERE price > ? AND id IN (?)
    `, 5000, []int64{1, 2, 3})

    if err != nil {
        log.Printf("构建 IN 查询失败: %v", err)
        return
    }

    err = db.Select(&expensiveProducts, db.Rebind(query), args...)
    if err != nil {
        log.Printf("IN 查询失败: %v", err)
        return
    }

    fmt.Println("价格大于5000的产品:")
    for _, p := range expensiveProducts {
        fmt.Printf("  - %s: ¥%.2f\n", p.Name, p.Price)
    }

    // 连接查询
    fmt.Println("\n2. 连接查询:")
    type OrderDetail struct {
        OrderID    int64     `db:"order_id"`
        UserName   string    `db:"user_name"`
        ProductName string  `db:"product_name"`
        Quantity   int       `db:"quantity"`
        Price      float64   `db:"price"`
        Amount     float64   `db:"amount"`
        CreatedAt  time.Time `db:"created_at"`
    }

    var orderDetails []OrderDetail
    joinQuery := `
        SELECT o.id as order_id, u.name as user_name, p.name as product_name,
               oi.quantity, oi.price, o.amount, o.created_at
        FROM orders o
        JOIN users u ON o.user_id = u.id
        JOIN order_items oi ON o.id = oi.order_id
        JOIN products p ON oi.product_id = p.id
        ORDER BY o.created_at DESC
    `

    err = db.Select(&orderDetails, joinQuery)
    if err != nil && err != sql.ErrNoRows {
        log.Printf("连接查询失败: %v", err)
        return
    }

    fmt.Println("订单详情:")
    for _, od := range orderDetails {
        fmt.Printf("  - 订单%d: %s 购买 %s × %d = ¥%.2f\n",
            od.OrderID, od.UserName, od.ProductName, od.Quantity, od.Amount)
    }

    // 聚合查询
    fmt.Println("\n3. 聚合查询:")
    type ProductStats struct {
        ProductName string  `db:"product_name"`
        TotalSales  float64 `db:"total_sales"`
        TotalQty    int     `db:"total_quantity"`
        AvgPrice    float64 `db:"avg_price"`
    }

    var stats []ProductStats
    statsQuery := `
        SELECT p.name as product_name,
               SUM(oi.quantity * oi.price) as total_sales,
               SUM(oi.quantity) as total_quantity,
               AVG(oi.price) as avg_price
        FROM order_items oi
        JOIN products p ON oi.product_id = p.id
        GROUP BY p.id, p.name
        HAVING total_sales > 0
        ORDER BY total_sales DESC
    `

    err = db.Select(&stats, statsQuery)
    if err != nil && err != sql.ErrNoRows {
        log.Printf("聚合查询失败: %v", err)
        return
    }

    fmt.Println("产品销售统计:")
    for _, s := range stats {
        fmt.Printf("  - %s: 销售额 ¥%.2f, 销量 %d, 均价 ¥%.2f\n",
            s.ProductName, s.TotalSales, s.TotalQty, s.AvgPrice)
    }
}

// 6. 事务处理示例
func demonstrateTransactions(db *Database) {
    fmt.Println("\n=== 事务处理示例 ===")

    // 开始事务
    tx, err := db.Beginx()
    if err != nil {
        log.Printf("开始事务失败: %v", err)
        return
    }
    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p)
        }
    }()

    // 创建用户
    user := User{
        Name:  "事务用户",
        Email: "transaction@example.com",
        Age:   30,
    }

    userQuery := `INSERT INTO users (name, email, age) VALUES (?, ?, ?)`
    result, err := tx.Exec(userQuery, user.Name, user.Email, user.Age)
    if err != nil {
        tx.Rollback()
        log.Printf("插入用户失败: %v", err)
        return
    }

    userID, _ := result.LastInsertId()

    // 创建订单
    orderQuery := `INSERT INTO orders (user_id, amount, status) VALUES (?, ?, ?)`
    result, err = tx.Exec(orderQuery, userID, 999.99, "completed")
    if err != nil {
        tx.Rollback()
        log.Printf("插入订单失败: %v", err)
        return
    }

    orderID, _ := result.LastInsertId()

    // 提交事务
    if err := tx.Commit(); err != nil {
        log.Printf("提交事务失败: %v", err)
        return
    }

    fmt.Printf("✅ 事务执行成功,创建用户 ID: %d, 订单 ID: %d\n", userID, orderID)
}

// 7. 预处理语句示例
func demonstratePreparedStatements(db *Database) {
    fmt.Println("\n=== 预处理语句示例 ===")

    // 准备预处理语句
    stmt, err := db.Preparex(`
        INSERT INTO products (name, price, description, stock, is_active)
        VALUES (?, ?, ?, ?, ?)
    `)
    if err != nil {
        log.Printf("准备预处理语句失败: %v", err)
        return
    }
    defer stmt.Close()

    // 批量执行预处理语句
    products := []struct {
        Name        string
        Price       float64
        Description string
        Stock       int
        IsActive    bool
    }{
        {"键盘", 299.99, "机械键盘", 100, true},
        {"鼠标", 199.99, "游戏鼠标", 150, true},
        {"显示器", 1999.99, "4K显示器", 20, true},
    }

    for _, p := range products {
        result, err := stmt.Exec(p.Name, p.Price, p.Description, p.Stock, p.IsActive)
        if err != nil {
            log.Printf("执行预处理语句失败: %v", err)
            continue
        }
        id, _ := result.LastInsertId()
        fmt.Printf("插入产品成功: %s (ID: %d)\n", p.Name, id)
    }
}

// 8. 高级映射功能
func demonstrateAdvancedMapping(db *Database) {
    fmt.Println("\n=== 高级映射功能 ===")

    // 使用结构体标签映射
    fmt.Println("\n1. 结构体映射:")
    rows, err := db.Queryx("SELECT * FROM products LIMIT 3")
    if err != nil {
        log.Printf("查询失败: %v", err)
        return
    }
    defer rows.Close()

    for rows.Next() {
        var product Product
        if err := rows.StructScan(&product); err != nil {
            log.Printf("扫描结构体失败: %v", err)
            continue
        }
        fmt.Printf("产品: %s, 价格: ¥%.2f, 库存: %d\n",
            product.Name, product.Price, product.Stock)
    }

    // MapScan 到 map
    fmt.Println("\n2. MapScan 示例:")
    row := db.QueryRowx("SELECT * FROM users ORDER BY id LIMIT 1")
    result := make(map[string]interface{})
    if err := row.MapScan(result); err != nil && err != sql.ErrNoRows {
        log.Printf("MapScan 失败: %v", err)
        return
    }

    fmt.Println("用户数据 (Map):")
    for key, value := range result {
        fmt.Printf("  %s: %v\n", key, value)
    }

    // SliceScan 到切片
    fmt.Println("\n3. SliceScan 示例:")
    var results [][]interface{}
    rows, err = db.Queryx("SELECT id, name, email FROM users LIMIT 2")
    if err != nil {
        log.Printf("查询失败: %v", err)
        return
    }
    defer rows.Close()

    for rows.Next() {
        rowData, err := rows.SliceScan()
        if err != nil {
            log.Printf("SliceScan 失败: %v", err)
            continue
        }
        results = append(results, rowData)
    }

    fmt.Println("用户数据 (Slice):")
    for i, row := range results {
        fmt.Printf("  行%d: ID=%v, 姓名=%v, 邮箱=%v\n", i+1, row[0], row[1], row[2])
    }
}

// 9. 分页查询示例
func demonstratePagination(db *Database) {
    fmt.Println("\n=== 分页查询示例 ===")

    type PaginationParams struct {
        Limit  int `db:"limit"`
        Offset int `db:"offset"`
    }

    // 分页查询用户
    pageSize := 2
    currentPage := 1
    offset := (currentPage - 1) * pageSize

    var users []User
    query := `SELECT * FROM users ORDER BY id LIMIT ? OFFSET ?`
    err := db.Select(&users, query, pageSize, offset)
    if err != nil {
        log.Printf("分页查询失败: %v", err)
        return
    }

    fmt.Printf("第%d页 (每页%d条):\n", currentPage, pageSize)
    for _, user := range users {
        fmt.Printf("  - ID: %d, 姓名: %s\n", user.ID, user.Name)
    }

    // 获取总数
    var total int
    err = db.Get(&total, "SELECT COUNT(*) FROM users")
    if err != nil {
        log.Printf("获取总数失败: %v", err)
        return
    }

    totalPages := (total + pageSize - 1) / pageSize
    fmt.Printf("总记录数: %d, 总页数: %d\n", total, totalPages)
}

// 10. 错误处理示例
func demonstrateErrorHandling(db *Database) {
    fmt.Println("\n=== 错误处理示例 ===")

    // 查询不存在的记录
    var nonExistentUser User
    err := db.Get(&nonExistentUser, "SELECT * FROM users WHERE id = ?", 9999)
    if err != nil {
        if err == sql.ErrNoRows {
            fmt.Println("✅ 正确处理空结果: 用户不存在")
        } else {
            log.Printf("查询错误: %v", err)
        }
    }

    // 唯一约束错误
    duplicateUser := User{
        Name:  "测试用户",
        Email: "zhang@example.com", // 假设这个邮箱已存在
    }

    _, err = db.Exec(
        "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
        duplicateUser.Name, duplicateUser.Email, duplicateUser.Age,
    )
    if err != nil {
        fmt.Printf("✅ 正确处理唯一约束错误: %v\n", err)
    }
}

// 11. 连接池监控
func demonstrateConnectionPool(db *Database) {
    fmt.Println("\n=== 连接池信息 ===")

    stats := db.Stats()
    fmt.Printf("连接池统计:\n")
    fmt.Printf("  最大打开连接数: %d\n", stats.MaxOpenConnections)
    fmt.Printf("  打开连接数: %d\n", stats.OpenConnections)
    fmt.Printf("  使用中连接数: %d\n", stats.InUse)
    fmt.Printf("  空闲连接数: %d\n", stats.Idle)
    fmt.Printf("  等待连接数: %d\n", stats.WaitCount)
    fmt.Printf("  等待时间: %v\n", stats.WaitDuration)
}

func main() {
    // 使用 SQLite 内存数据库进行演示
    dsn := ":memory:"
    // 生产环境可以使用 MySQL:
    // dsn := "username:password@tcp(localhost:3306)/database?parseTime=true"

    db, err := NewDatabase("sqlite3", dsn)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 初始化数据库表结构
    if err := db.InitSchema(); err != nil {
        log.Fatal(err)
    }

    // 运行各种示例
    demonstrateBasicCRUD(db)
    demonstrateNamedParameters(db)
    demonstrateComplexQueries(db)
    demonstrateTransactions(db)
    demonstratePreparedStatements(db)
    demonstrateAdvancedMapping(db)
    demonstratePagination(db)
    demonstrateErrorHandling(db)
    demonstrateConnectionPool(db)

    fmt.Println("\n🎉 所有 SQLx 示例执行完成!")
}

SQLx 关键特性

  1. ​ 核心优势 ​

​ 更简洁的代码 ​:相比标准库减少大量样板代码

​ 命名参数 ​::name 格式更易读易维护

​ 自动映射 ​:查询结果自动映射到结构体

​ 类型安全 ​:编译时类型检查

  1. ​ 主要方法 ​

​Get()​:查询单条记录到结构体

​Select()​:查询多条记录到结构体切片

​NamedExec()​:使用命名参数执行

​Queryx()​:返回可扫描的 *sqlx.Rows

​Preparex()​:预处理语句

  1. ​ 高级功能 ​

​sqlx.In()​:简化 IN 查询

​Rebind()​:适配不同数据库的占位符

​StructScan()​:行数据扫描到结构体

​MapScan()​:行数据扫描到 map

  1. ​ 最佳实践 ​

​ 使用结构体标签 ​:db:“column_name”

​ 错误处理 ​:检查 sql.ErrNoRows

​ 连接池配置 ​:合理设置连接参数

​ 预处理语句 ​:重复查询使用预处理

​ 事务管理 ​:正确处理事务回滚

SQLx 在保持 SQL 灵活性的同时,大大提升了开发效率,相比 gorm 性能更高,是 Go 语言数据库操作的首选库之一。

通关密语:sqlx