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 关键特性
- 核心优势
更简洁的代码 :相比标准库减少大量样板代码
命名参数 ::name 格式更易读易维护
自动映射 :查询结果自动映射到结构体
类型安全 :编译时类型检查
- 主要方法
Get():查询单条记录到结构体
Select():查询多条记录到结构体切片
NamedExec():使用命名参数执行
Queryx():返回可扫描的 *sqlx.Rows
Preparex():预处理语句
- 高级功能
sqlx.In():简化 IN 查询
Rebind():适配不同数据库的占位符
StructScan():行数据扫描到结构体
MapScan():行数据扫描到 map
- 最佳实践
使用结构体标签 :db:“column_name”
错误处理 :检查 sql.ErrNoRows
连接池配置 :合理设置连接参数
预处理语句 :重复查询使用预处理
事务管理 :正确处理事务回滚
SQLx 在保持 SQL 灵活性的同时,大大提升了开发效率,相比 gorm 性能更高,是 Go 语言数据库操作的首选库之一。
通关密语:sqlx