🎬 前言:为什么是 Queryx?------ 因为 bug 等不及你「运行时才发现」
想象一下这些经典场景:
go
// 🚨 GORM 的"惊喜盲盒"
db.Where("nmae = ?", "john").Find(&users) // 拼写错误?编译器:没问题 👌
// → 运行时:查不到数据?🤔 调试 2 小时:哦,`nmae` 少了个 `e` ......
// 🧨 原生 SQL 的"类型彩票"
rows, _ := db.Query("SELECT * FROM users WHERE age > ?", "18")
// 字符串传数字?MySQL:我先 convert 一下~(可能报错,也可能静默截断!)
💡 Queryx 的承诺 :
"你写的每一个字段、每一条条件,都经过 Go 编译器的'安检门'------
错别字?类型错?关联漏?------ 编译就报错,绝不拖到生产环境!"
🧁 第一章:安装 Queryx ------ 比泡面还快,比点外卖还省心
bash
# 方法一:一键安装(官方推荐脚本)
curl -sf https://raw.githubusercontent.com/swiftcarrot/queryx/main/install.sh | sh
# 方法二:Go install(适合 CI/CD)
go install github.com/swiftcarrot/queryx/cmd/queryx@latest
✅ 安装后终端多一个 queryx 命令------你的 数据库自动化管家 正式上岗!🎩
(它会帮你生成代码、做迁移、检查 schema,就是不帮你写业务逻辑 😏)
📝 第二章:定义数据模型 ------ 像写情书一样优雅
创建 schema.hcl 文件(Queryx 的"魔法配方书"):
hcl
# schema.hcl
database "db" {
adapter = "postgresql" # 也支持 mysql/sqlite
config "development" {
url = "postgres://postgres:postgres@localhost:5432/blog_dev?sslmode=disable"
}
config "production" {
url = env("DATABASE_URL") # 🛡️ 敏感信息走环境变量!
}
# 👉 关键一步:指定生成 Go 客户端
generator "client-golang" {}
}
# 定义 User 模型 ------ 像描述你的理想型
model "User" {
column "name" {
type = string
null = false # 不能为空,就像爱情不能将就 💖
}
column "email" {
type = string
unique = true # 邮箱唯一,像身份证号
}
column "age" {
type = integer
null = true # 年龄?可以是秘密~
}
column "created_at" {
type = datetime
default = "now()" # 自动填充,真贴心!
}
}
# Post 模型:因为用户总要发帖子
model "Post" {
belongs_to "user" {} # 自动加 user_id 外键!
column "title" { type = string }
column "content" { type = text }
}
✅ HCL(HashiCorp Configuration Language) 的优势:
- 比 YAML 不易缩进出错
- 比 JSON 支持注释 + 表达式
- 比 Go Struct 更专注 数据建模,而非实现细节
🪄 第三章:数据库迁移 ------ 像变魔术一样丝滑
bash
# 1. 创建数据库(如果不存在)
queryx db:create
# 2. 生成迁移文件(基于 schema.hcl)
queryx db:migrate
# 3. 应用迁移(建表 + 约束 + 索引)
queryx db:migrate:up
🔧 运行后:
- 自动生成
migrations/目录 + 版本化 SQL - 自动建
users、posts表,带外键、唯一索引、默认值 - 你的双手终于从
CREATE TABLE中解放了!
🏠 比喻时间 :
你告诉管家:"我要一个两居室,主卧朝南,带智能马桶"
管家用
schema.hcl记下需求 →queryx db:migrate→ 交钥匙!✨
🎮 第四章:CRUD 操作 ------ 比打游戏刷副本还爽!
先生成 Go 代码(让 Queryx 为你打工):
bash
queryx g # or queryx generate
# → 生成 db/ 目录:含 client、models、builders、migrations...
然后在代码中享受编译时类型安全的快乐:
4.1 🎉 创建用户:像迎接新朋友一样温暖
go
package main
import (
"context"
"fmt"
"log"
"your-project/db" // ← Queryx 生成的包!
)
func main() {
c, err := db.NewClient(context.Background())
if err != nil {
log.Fatal("💔 连不上数据库")
}
defer c.Close()
// ✅ 类型安全!SetName 只接受 string,SetAge 只接受 *int
newUser := c.ChangeUser().
SetName("Go语言小王子").
SetEmail("gopher@example.com").
SetAge(db.Int(25)) // 注意:nil-safe,db.Int(nil) 表示 NULL
user, err := c.QueryUser().Create(newUser)
if err != nil {
log.Fatal("创建失败:", err) // 可能是邮箱重复!
}
fmt.Printf("✅ 用户创建成功!ID: %d, 姓名: %s\n", user.ID, user.Name)
// 输出:ID: 1, 姓名: Go语言小王子
}
🔍 关键细节(来自 Queryx 真实设计):
c.ChangeUser()→ 返回 变更构建器(Builder Pattern)SetAge(db.Int(25)):用包装类型支持NULL(db.Int(nil))- 所有
SetXxx()方法:编译时报错!写错字段?Go 编译器秒拒!
4.2 🔍 查询用户:比福尔摩斯找线索还准
go
// 精准查找:按 ID
func findUserByID(c *db.Client, id int64) {
user, err := c.QueryUser().Find(id)
if err != nil {
fmt.Println("🫣 用户不存在")
return
}
fmt.Printf("👤 %s (%s, %d岁)\n", user.Name, user.Email, user.Age)
}
// 条件查询:链式 API 如诗如画
func queryUsers(c *db.Client) {
// 找 18+ 用户,按创建时间倒序
adults, err := c.QueryUser().
Where(c.User.Age.GT(18)). // 年龄 > 18
OrderBy(c.User.CreatedAt.Desc()). // 时间倒序
All()
if err != nil {
log.Fatal("查询翻车了")
}
fmt.Printf("👥 共 %d 位成年用户:\n", len(adults))
for _, u := range adults {
fmt.Printf("• %s(%d岁)\n", u.Name, u.Age)
}
// 邮箱精准匹配(编译时检查字段!)
user, err := c.QueryUser().
Where(c.User.Email.EQ("gopher@example.com")).
First()
if err == nil {
fmt.Printf("📧 邮箱主人:%s\n", user.Name)
}
}
💡 Queryx 查询构建器三大法宝:
c.User.Age.GT(18)------ 字段路径 + 操作符(GT/LT/EQ/IN...)OrderBy(...).Limit(...).Offset(...)------ 链式组合First()/All()/Count()------ 清晰语义,告别Scan地狱
4.3 ✏️ 更新用户:像美颜相机一样精准
go
func updateUser(c *db.Client, id int64) {
// 先查后改(乐观锁友好)
user, err := c.QueryUser().Find(id)
if err != nil {
return
}
// 构建更新:类型安全!
update := c.ChangeUser().
SetName("Go语言大神").
SetAge(db.Int(26)).
SetEmail("master@golang.com")
err = user.Update(update)
if err != nil {
log.Fatal("更新失败")
}
fmt.Println("✨ 用户信息已升级!")
// 批量更新:全公司员工 +1 岁(生日快乐🎂)
count, err := c.QueryUser().
Where(c.User.Age.IsNotNull()). // 只更新非 NULL 年龄
UpdateAll(c.ChangeUser().SetAge(c.User.Age.Add(1)))
if err == nil {
fmt.Printf("🎉 给 %d 位同事过了生日!\n", count)
}
}
🌟 亮点:
c.User.Age.Add(1)→ 生成age = age + 1,避免竞态UpdateAll支持表达式更新,无 N+1 问题
4.4 🗑️ 删除用户:比分手还干净利落
go
func deleteUser(c *db.Client, id int64) {
// 方式1:先查后删(适合带业务逻辑校验)
user, _ := c.QueryUser().Find(id)
if user != nil {
err := user.Delete() // 软删除?硬删除?schema 决定!
if err == nil {
fmt.Println("✅ 用户已删除")
}
}
// 方式2:条件批量删(高效!)
count, err := c.QueryUser().
Where(c.User.Age.LT(18)). // 未成年
DeleteAll()
if err == nil {
fmt.Printf("👮 清理了 %d 位未成年用户\n", count)
}
}
⚠️ 安全设计 :
Queryx 默认开启软删除 (如有
deleted_at字段),真要硬删?需显式调用
ForceDelete()------ 防手抖第一道防线!
🤝 第五章:关联查询 ------ 像社交网络一样自然
go
// 预加载帖子(1 次查询搞定,无 N+1!)
func userWithPosts(c *db.Client, userID int64) {
user, err := c.QueryUser().
Preload(c.User.Posts). // ← 关键!生成 JOIN 或 IN 查询
Find(userID)
if err != nil {
log.Fatal("查询失败")
}
fmt.Printf("📝 %s 的博客:\n", user.Name)
for _, p := range user.Posts {
fmt.Printf("• %s\n", truncate(p.Title, 30))
}
}
// 创建「用户+帖子」组合数据
func createUserWithPosts(c *db.Client) {
// 1. 创建用户
user, _ := c.QueryUser().Create(
c.ChangeUser().SetName("博客达人").SetEmail("blogger@go.com"),
)
// 2. 关联创建帖子(自动填 user_id!)
posts := []db.CreatePostInput{
{Title: "我的第一个 Go 程序", Content: "Hello, Queryx!"},
{Title: "为什么我爱 Queryx", Content: "类型安全让我睡得更香~"},
}
for _, p := range posts {
_, _ = c.QueryPost().Create(
c.ChangePost().
SetTitle(p.Title).
SetContent(p.Content).
SetUserID(user.ID), // ← 类型安全!ID 是 int64
)
}
fmt.Printf("📚 用户 %s 发布了 2 篇博文!\n", user.Name)
}
// 辅助:截断长文本
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "..."
}
✅ 关联优势:
Preload(c.User.Posts)→ 自动识别belongs_to关系- 生成高效 SQL(
JOIN或SELECT ... WHERE id IN (...))- 编译时检查关联路径:
c.User.Postxxx?❌ 不存在!
🧙 第六章:高级技巧 ------ 成为 Queryx 高手
6.1 💰 事务处理:要么全成功,要么全回滚
go
func transferPoints(c *db.Client, fromID, toID int64, pts int) error {
tx, err := c.Begin(context.Background())
if err != nil {
return err
}
defer tx.Rollback() // ← 忘记这行?defer 保你平安
// 扣款方
from, err := tx.QueryUser().Find(fromID)
if err != nil || from.Points < pts {
return fmt.Errorf("余额不足")
}
err = from.Update(c.ChangeUser().SetPoints(from.Points - pts))
if err != nil {
return err
}
// 收款方
to, err := tx.QueryUser().Find(toID)
if err != nil {
return err
}
err = to.Update(c.ChangeUser().SetPoints(to.Points + pts))
if err != nil {
return err
}
return tx.Commit() // 🎯 提交!资金到位!
}
🔐 事务中所有操作走
tx.QueryUser(),非c.QueryUser()------ Queryx 强制你区分!
6.2 📊 复杂查询:像搭乐高一样组合
go
func userStats(c *db.Client) {
// 统计:COUNT + 条件
total, _ := c.QueryUser().Count()
adults, _ := c.QueryUser().Where(c.User.Age.GTE(18)).Count()
active, _ := c.QueryUser().Where(c.User.LastLogin.GT(time.Now().Add(-30*24*time.Hour))).Count()
fmt.Printf(`📊 用户大盘:
总用户 : %d
成年用户 : %d
近30天活跃: %d
`, total, adults, active)
}
// 分组统计:年龄分布
func ageDistribution(c *db.Client) {
var results []struct {
AgeRange string `db:"age_range"`
Count int `db:"count"`
}
// Queryx 支持 Raw SQL 片段(紧急时的"创可贴")
err := c.QueryRaw(`
SELECT
CASE
WHEN age < 18 THEN '未成年'
WHEN age BETWEEN 18 AND 35 THEN '青壮年'
ELSE '资深青年'
END AS age_range,
COUNT(*) AS count
FROM users
WHERE age IS NOT NULL
GROUP BY age_range
`).Scan(&results)
// ...
}
✅ 原则:
- 95% 场景用类型安全 Builder
- 5% 高级 SQL 用
QueryRaw(),但参数仍走?占位防注入
⚖️ 第七章:Queryx vs 其他方案 ------ 谁才是你的真命天"库"?
| 特性 | Queryx ✅ | GORM 🟡 | 原生 SQL ❌ |
|---|---|---|---|
| 类型安全 | ✅ 编译时报错 | ❌ 运行时才发现 | ❌ 全靠人眼 |
| 性能 | ✅ 零反射,预生成代码 | 🟡 反射开销(中小项目可忍) | ✅ 最快 |
| 开发体验 | ✅ IDE 自动补全 + 跳转 | ✅ 功能丰富 | ❌ 易错、难维护 |
| 学习曲线 | ✅ 1 0分钟上手 | 🟡 需理解 Scopes/Hooks | ✅ 会 SQL 就行 |
| 关联查询 | ✅ Preload() 防 N+1 |
✅ .Preload() |
❌ 手写 JOIN 易出错 |
| 迁移管理 | ✅ queryx db:migrate |
✅ AutoMigrate() |
❌ 手动维护 |
🎯 选型建议:
- 想掌控 SQL、讨厌魔法、追求轻量高效 → Queryx
- 团队已重度依赖 GORM、需快速 CRUD → GORM
- 做超高性能场景、复杂报表 → 原生 SQL + queryx.QueryRaw()
🎁 结语:用 Queryx 的一天,是安心写代码的一天
go
// 用 Queryx 的幸福时刻 👇
user, err := c.QueryUser().
Where(c.User.Email.EQ("happy@coder.com")).
Preload(c.User.Posts).
First()
// 而不是:
// 🤯 "为什么字段是空的?哦,struct tag 写错了..."
// 🤯 "为什么报错?哦,SQL 拼错了关键字..."
// 🤯 "为什么慢?哦,N+1 查询了 1000 次..."
🌈 Queryx 的哲学 :
"我们不 hide SQL ------ 我们让 SQL 写得更安全、更快乐、更 Go。"
🚀 现在就试试吧!
-
go install github.com/swiftcarrot/queryx/cmd/queryx@latest -
写
schema.hcl -
queryx db:create && queryx g -
感受编译时报错带来的安全感 ❤️
最后一句真心话 :
"早用 Queryx,少 debug 2 小时;
晚用 Queryx,多加班 3 通宵。"
Happy Querying! 🍵🐉
(奶茶已下单,代码正在跑~)