ToolkitX
知识库工具箱

数据库集成

database/sql, GORM, 事务

25min·进阶

01. database/sql 基础

Go 标准库的 database/sql 包提供了统一的数据库操作接口,但实际驱动需要从第三方导入(如 go-sql-driver/mysql、lib/pq)。sql.Open 打开连接池但不验证连接,db.Ping 才真正测试连接。Exec 执行增删改,Query 执行查询,QueryRow 查单行。
go
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql" // 匿名导入注册驱动
)

func main() {
    db, err := sql.Open("mysql", "root:123456@tcp(localhost:3306)/mydb?parseTime=true")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    if err = db.Ping(); err != nil {
        log.Fatal(err)
    }
    
    fmt.Println("数据库连接成功")
}
DSN 里加上 parseTime=true,这样 Scan 时间字段时自动转为 time.Time。

02. CRUD 操作

增删改用 Exec,返回 Result 可以拿 LastInsertId 和 RowsAffected。查询用 Query 返回 Rows,必须遍历调用 rows.Next() 然后 rows.Scan() 取出数据,最后 rows.Close()。单行查询用 QueryRow 配合 Scan,如果没找到返回 sql.ErrNoRows。
go
// 增
result, err := db.Exec("INSERT INTO users(name, age) VALUES(?, ?)", "小明", 18)
id, _ := result.LastInsertId()

// 查多条
rows, err := db.Query("SELECT id, name FROM users WHERE age > ?", 18)
defer rows.Close()
for rows.Next() {
    var id int
    var name string
    rows.Scan(&id, &name)
    fmt.Println(id, name)
}

// 查单条
var name string
err = db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name)
if err == sql.ErrNoRows {
    fmt.Println("没找到")
}
Query 返回的 rows 必须 Close,不然连接不会释放回池子导致连接泄漏。

03. sqlx 库的使用

database/sql 的 Scan 要手动写很多 & 变量,字段多了很烦。sqlx 库通过结构体 tag 自动映射,StructScan 一行代码把行数据映射到结构体。支持命名参数、In 查询(处理 IN (?) 的底层逻辑)等。在 sql 包的基础上封装,不会改变编程模型,上手很快。
go
import "github.com/jmoiron/sqlx"

type User struct {
    ID    int    `db:"id"`
    Name  string `db:"name"`
    Age   int    `db:"age"`
}

db, _ := sqlx.Connect("mysql", "...")

// 查询自动映射到结构体
var users []User
db.Select(&users, "SELECT * FROM users WHERE age > ?", 18)

// 查询单条
var user User
err := db.Get(&user, "SELECT * FROM users WHERE id = ?", 1)

// 命名参数
namedStmt, _ := db.PrepareNamed("SELECT * FROM users WHERE name = :name")
namedStmt.Get(&user, map[string]interface{}{"name": "小明"})
sqlx 对标准 sql 包零侵入,你可以渐进式地混用 sqlx 和 sql。

04. GORM 入门

GORM 是 Go 最流行的 ORM 库。定义模型结构体嵌 gorm.Model 自动获得 ID、CreatedAt、UpdatedAt、DeletedAt 字段。AutoMigrate 自动建表或更新表结构,Create 增,Find 查,Save 改,Delete 删。链式调用支持条件、排序、分页等,开发效率高。
go
type User struct {
    gorm.Model
    Name  string `gorm:"size:100;not null"`
    Age   int    `gorm:"default:0"`
    Email string `gorm:"uniqueIndex"`
}

db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db.AutoMigrate(&User{})

// 增
db.Create(&User{Name: "小明", Age: 18})

// 查
var user User
db.First(&user, 1)                         // 主键查
db.Where("name = ?", "小明").First(&user)  // 条件查

// 改
db.Model(&user).Update("age", 20)

// 删(软删除)
db.Delete(&user)
GORM 的软删除是通过 DeletedAt 字段实现的,Delete 只是标记删除不是真删。Unscoped 可以真删。

05. 数据库事务

Go 中事务通过 db.Begin 开启,返回的 Tx 对象执行所有 SQL,最后 tx.Commit 或 tx.Rollback。事务最好用完就结束,不要在事务里做耗时操作(发邮件、调外部 API 等)。GORM 的事务可以用闭包方式自动处理提交和回滚。
go
// 标准 sql 事务
func transferMoney(db *sql.DB, from, to int, amount float64) error {
    tx, err := db.Begin()
    if err != nil { return err }
    defer tx.Rollback() // 如果没 Commit,回滚
    
    _, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)
    if err != nil { return err }
    
    _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)
    if err != nil { return err }
    
    return tx.Commit()
}

// GORM 事务闭包
db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil { return err }
    if err := tx.Create(&order).Error; err != nil { return err }
    return nil // 返回 nil 自动提交,返回 err 自动回滚
})
defer tx.Rollback() 是个好习惯,Commit 之前 Rollback 无影响,Commit 之后 Rollback 会被忽略。

知识测验

1/4正确 0

database/sql 中 Exec 和 Query 的区别?

下一节

测试与部署

下一节