Files

可选 GORM 适配器

从 v0.14.1-preview 提供。gormx 把 GORM 接到 go-omni 已初始化的 *sql.DB,保留一份连接池和一处资源所有权。使用 GORM v2 API(本版验收版本为 gorm.io/gorm v1.31.2、MySQL dialect v1.6.0)。

核心 omni、database、database/mysql 不导入此包;普通项目的编译依赖链不包含 GORM。仓库同一 Go module 的 go.mod 会记录可选适配器依赖,使用方显式导入 database/gormx 才编译它。本版没有新增默认 ORM 或 --orm 脚手架开关。

生成项目接入

go install git.yuntuo.hk/k2safe/go-omni/cmd/go-omni@v0.14.1-preview
go-omni new --database mysql --module example.com/billing ./billing

按 MySQL 配置说明 设置连接信息、TLS 和池大小。在开发者维护的 internal/svc/serviceContext.go 中增加 ORM *gorm.DB 字段,并填写现有 Init:

import (
    "context"
    "git.yuntuo.hk/k2safe/go-omni/database/gormx"
    "gorm.io/gorm"
)

// 向现有 ServiceContext 添加 ORM *gorm.DB 字段。
// 保留 Runtime/Config/Logger/DB 等现有字段。
func (c *ServiceContext) Init(ctx context.Context) error {
    orm, err := gormx.MySQL(ctx, c.DB, gormx.WithLogger(c.Logger))
    if err != nil {
        return err
    }
    c.ORM = orm
    return nil
}

执行 go mod tidy。main、runtimeGen、Handler 和路由无需修改;重新生成保留 svc 的字段与 Init。已有项目先 go get git.yuntuo.hk/k2safe/go-omni@v0.14.1-preview,同样接入;v0.14.0 生成项目的目录和生命周期契约不变。

Repository 接收 *gorm.DB,查询使用请求 context:

err := repo.db.WithContext(ctx).Where("id = ?", id).First(&item).Error

err = repo.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
    // 同一事务内始终使用 tx。
    return tx.Create(&item).Error
})

现有模型、Hooks、Scopes、事务、命名策略和错误处理继续由项目维护。共享连接池不会把分别发起的 SQL/GORM 事务自动合并;不要在一个事务内切回根 ORM 或 c.DB 执行本应属于该事务的写入。

连接池与失败处理

  • MySQL 配置、TLS、字符集、会话参数、池大小及健康检查由底层 database/mysql 管理,适配器不再设置另一套 DSN 或池参数。
  • GORM 方言的版本协商继续执行,自动 Ping 关闭以复用已完成的池初始化。初始化查询使用传入 context,并受池的 PingTimeout 限制;可用 WithInitTimeout(3*time.Second) 单独指定。
  • 返回成功后不保留启动 context,每次查询用 WithContext(ctx)。查询取消、错误、事务提交/回滚保持 GORM 原生语义。
  • 对方言初始化失败时 GORM 自动关闭连接池的路径提供保护;错误仍保留 errors.Is/As,原池继续归调用方管理。生成项目的 Init 返回错误时,再由 Runtime 统一回滚该项目资源。
  • 服务排空后由 go-omni 关闭原池;orm.DB() 返回的仍是那一个池。不要给它重复登记 Close,也不要让 Repository 关闭它。

适配器自身不创建后台 worker、不自动建表、不执行 AutoMigrate、不改写默认事务或全表更新保护,也不替业务重试写入。

SQL 日志

默认借用当前 context 的 logx,也可显式 WithLogger(c.Logger)。每个 ORM 可以使用独立日志实例;不会改动进程全局 Logger。

sqlLog := gormx.DefaultLogConfig()
sqlLog.Level = "info" // silent / error / warn / info
sqlLog.SlowThreshold = 300*time.Millisecond

orm, err := gormx.MySQL(ctx, c.DB,
    gormx.WithLogger(c.Logger),
    gormx.WithLogConfig(sqlLog),
    gormx.WithConfig(gorm.Config{
        TranslateError: true,
        // NamingStrategy 等原生选项可按项目添加。
    }),
)

默认记录错误和超过 200 ms 的慢查询,忽略 ErrRecordNotFound 的错误事件;SlowThreshold: 0 关闭慢查询事件。事件为 gorm.query、gorm.slowQuery、gorm.queryFailed,携带 durationMs、rowsAffected、sql 和 context 中的 requestId/traceId。GORM 的 Debug()/LogMode 使用副本,不改变其他请求的日志级别;最终输出还受 logx 自身级别控制。

默认不插入绑定参数、不记录驱动完整错误文案,只记录错误类别;SQL 正文最多保留 4096 字节,截断后附加省略号。明确开启 IncludeParameters / IncludeErrorDetails 才输出这些内容。日志不会解析和脱敏直接写在原始 SQL 里的字面量,查询仍应使用占位符。仅需要日志适配的既有 GORM 实例,也可使用 NewLogger(logger, config) 返回的原生 logger.Interface。

WithConfig 中若显式设置 gorm.Config.Logger,则使用项目自己的 Logger,该 Logger 自行负责脱敏和上下文。配置和 Plugins/ClauseBuilders 容器会复制;用户自定义插件、NamingStrategy 等对象仍由用户保证并发安全。

其他方言与高级配置

MySQL 便捷入口封装了下面的通用入口:

orm, err := gormx.Open(ctx, pool,
    func(conn gorm.ConnPool) gorm.Dialector {
        return mysql.New(mysql.Config{Conn: conn})
    },
    gormx.WithLogger(logger),
)

其他方言可由项目提供相同形式的工厂,必须使用传入的 Conn,不能创建或替换连接池。本版真实数据库验收覆盖 MySQL 8.4,不代表其他数据库已经联调。

不能通过 WithConfig 的 ConnPool/Dialector 覆盖此约定。初始化期间不暴露裸 sql.DB;需要它的自定义插件应在 Open 成功后显式注册。创建额外池或替换 ConnPool 的路由插件不属于本适配器的一池所有权范围。

PrepareStmt 默认关闭;若显式开启,保留 GORM 的缓存行为,容量、TTL 和缓存清理由项目按 GORM 接口管理。当前固定的 GORM 版本,PreparedStmtDB.Close 只清理语句,不提供停止其缓存清理 goroutine 的公开接口;因此应在服务初始化时创建一次 ORM,避免按请求反复创建。适配器不修改第三方缓存实现,也不宣称解决其生命周期限制。

参考:GORM 现有连接池、GORM Logger。本版验收见 GORM 验收报告。