🚀 我写了一个让 GORM 查询不再写字符串的库:gorm-query
🚀 我写了一个让 GORM 查询不再写字符串的库:gorm-query
在 Go 生态中,GORM 是最流行的 ORM 之一。
但在中大型项目里,我们经常会遇到一些非常典型的问题:
查询条件大量使用字符串
Repository 方法爆炸
事务逻辑污染业务代码
为了解决这些问题,我写了一个开源库:
👉 gorm-query
github.com/im-wmkong/gorm-query
它为 GORM 提供了一套组合方案:
强类型查询 + Query Builder + 泛型 Repository + Context 事务
💥 一次线上 Bug,让我重新思考 GORM 查询
几个月前,我们线上出现了一个非常隐蔽的问题。
某个接口突然报错:
Unknown column 'user_name'
排查后发现代码是这样的:
db.Where("user_name = ?", name)
但数据库字段其实已经改成:
username
于是事情变成了这样:
代码 ✅ 编译正常
CI ✅ 测试正常
代码 ✅ 成功上线
用户 ❌ 访问接口直接报错
原因其实很简单:
GORM 查询字段是字符串,IDE 和编译器都无法检查。
这让我重新思考一个问题:
为什么 Go ORM 查询还在大量使用字符串?
🤕 GORM 在中大型项目里的三个痛点
1️⃣ 魔法字符串
最常见的代码:
db.Where("age >= ?", 18).
Where("user_name LIKE ?", "%wmkong%")
问题很明显:
字段写错 ❌ 编译不报错
IDE ❌ 没有自动补全
重构 ❌ 无法自动修改
2️⃣ Repository 方法爆炸
很多项目会写 Repository:
type UserRepository interface {
FindByName(ctx context.Context, name string) ([]*User, error)
FindByAge(ctx context.Context, age int) ([]*User, error)
FindByStatus(ctx context.Context, status int) ([]*User, error)
}
随着业务增长,很快就会变成:
FindByNameAndStatus
FindByAgeAndStatus
FindByEmailAndStatus
FindByStatusWithPage
Repository 很容易膨胀到几十个方法。
3️⃣ 事务污染业务层
很多代码会这样写:
func (s *UserService) CreateUser(tx *gorm.DB, user *User)
结果就是:
*gorm.DB在项目中到处传递Service 层和 ORM 强耦合
💡 gorm-query 的解决方案
gorm-query 的核心思路其实很简单:
强类型字段 + Query Builder + 泛型 Repository + Context 事务
目标是让查询代码变成这样:
query.New().
Where(
UserProps.Age.Gte(18),
UserProps.UserName.Contains("wmkong"),
).
Order(UserProps.ID.Desc())
而不是这样:
db.Where("age >= ?", 18)
🧩 强类型字段:告别字符串查询
gorm-query 通过 代码生成 创建字段对象。
Model 示例:
//go:generate go run github.com/im-wmkong/gorm-query/cmd/gen-props@latest -type=User
type User struct {
gorm.Model
UserName string `gorm:"column:user_name"`
Email string
Age int
Status int
}
执行代码生成:
go generate ./...
生成代码示例:
type userProps struct {
UserName query.Column
Email query.Column
Age query.Column
Status query.Column
}
var UserProps = userProps{
UserName: "user_name",
Email: "email",
Age: "age",
Status: "status",
}
于是查询可以写成:
UserProps.UserName
UserProps.Age
IDE 可以自动补全字段名 👍
🧱 Query Builder:灵活组合查询
Query Builder 用来构建查询条件:
qb := query.New().
Where(
UserProps.Age.Gte(18),
UserProps.Status.Eq(1),
).
Order(UserProps.ID.Desc()).
Page(1, 20)
最终应用到 GORM:
err := qb.Apply(db.Model(&User{})).Find(&users).Error
相比字符串查询:
db.Where("age >= ?", 18)
Builder 方式更加:
类型安全
可组合
易复用
🏗 BaseRepository:让 Repository 回归极简
在传统架构中,Repository 同时承担两件事情:
数据访问 + 查询构建
而在 gorm-query 中,这两个职责被拆开:
Query Builder 负责构建查询
Repository 负责执行查询
gorm-query 提供了一个泛型仓储:
repo.BaseRepository[T]
它内置常见操作:
Create
Update
Delete
Find
First
Count
定义 Repository 非常简单:
type UserRepository struct {
repo.BaseRepository[model.User]
}
func NewUserRepository(dbClient db.Client) *UserRepository {
return &UserRepository{
repo.New[model.User](dbClient),
}
}
这样就已经拥有完整 CRUD 能力。
🔗 Builder + Repository
当 Query Builder 和 BaseRepository 结合后:
qb := query.New().
Where(UserProps.Status.Eq(1)).
Where(UserProps.Age.Gte(18))
users, err := userRepo.Find(ctx, qb)
执行流程如下:
Query Builder
↓
BaseRepository.Find
↓
qb.Apply(db)
↓
GORM 执行 SQL
🔄 Context 事务管理
gorm-query 通过 Context 传递事务。
初始化:
dbClient := db.NewClient(gormDB)
在 Service 中:
func (s *UserService) CreateUserAndProfile(ctx context.Context, user *User, profile *Profile) error {
return s.tm.Transaction(ctx, func(txCtx context.Context) error {
if err := s.userRepo.Create(txCtx, user); err != nil {
return err
}
profile.UserID = user.ID
if err := s.profileRepo.Create(txCtx, profile); err != nil {
return err
}
return nil
})
}
Service 层 完全不需要接触 *gorm.DB。
🧪 动态查询示例
func (s *UserService) GetUsers(ctx context.Context, name string, minAge int) ([]*User, error) {
qb := query.New().
Where(UserProps.Status.Eq(1))
if name != "" {
qb = qb.Where(UserProps.UserName.Contains(name))
}
if minAge > 0 {
qb = qb.Where(UserProps.Age.Gte(minAge))
}
return s.userRepo.Find(ctx, qb)
}
Repository 无需新增任何方法。
🧭 项目架构
整体架构如下:
Service Layer
│
▼
Query Builder
│
▼
Repository (BaseRepository[T])
│
▼
gorm-query
│
▼
GORM
│
▼
Database
核心思想:
Builder + Repository + Context Transaction
⭐ 项目地址
github.com/im-wmkong/gorm-query
如果你在使用 GORM,并希望拥有:
强类型查询
灵活的查询组合
极简 Repository
优雅事务管理
欢迎试试 gorm-query,也欢迎 Star ⭐。
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
这个强类型不错 :+1:,楼主看看能不能完全独立出来,不依赖gorm?