ormx

package module
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 16 Imported by: 0

README

ormx

基于 GORM 的 MySQL 数据访问封装,包含两个包:

用途
github.com/gtkit/ormx 基于 GORM 的客户端——连接与连接池配置、事务死锁自动重试、单机健康探活与可观测
github.com/gtkit/ormx/zlogger GORM 的 zap 日志适配——慢查询阈值、trace id 提取、SQL 参数脱敏

安装

go get github.com/gtkit/ormx

根包 ormx(GORM 客户端)

快速开始
import "github.com/gtkit/ormx"

client, err := ormx.Open(ctx,
    ormx.WithHost("127.0.0.1"),
    ormx.WithPort("3306"),
    ormx.WithDatabase("app"),
    ormx.WithUser("root"),
    ormx.WithPassword("secret"),
)
if err != nil {
    return err
}
defer client.Close()

db := client.DB() // *gorm.DB,直接走 GORM API

配置里已有现成 DSN 时,可用 ormx.WithDSN("user:pass@tcp(host:3306)/db?parseTime=true") 一行替代上面的连接 Option(语义与限制见下文选项表)。

打开方式
入口 说明
ormx.Open(ctx, opts...) 按 Option 构建配置并连接,最常用
ormx.MustOpen(ctx, opts...) 同上,失败时 panic,适合启动期 wiring
ormx.OpenWithDB(ctx, sqlDB, opts...) 复用已有 *sql.DB(只应用显式传入的池 Option);打开成功后 Close() 不关闭外部 DB,但初始化失败时 GORM 可能关闭该 DB,失败后勿再复用(详见 GoDoc)
ormx.NewConfig(opts...) / cfg.With(opts...) / cfg.Open(ctx) 先构建 Config 值再打开,适合多实例复用基础配置(Config 是运行期配置,配置文件请由业务侧 DTO 转成 Option,详见 Config 的 GoDoc)

Config 通过 With / Clone 返回隔离副本(仅复制包内可变字段:SystemVariables map、连接池与方言指针),不修改原值;注入的 GORM.LoggerHealthProbeTxRetryObserverNamingStrategy.NameReplacerLoc 仍为共享引用。普通赋值(cfg2 := cfg)是浅拷贝,需独立副本时用 Clone/WithConfig.String()(及 MySQLConfig.String())与 %v / %+v / %#v 输出会把密码、参数值与连接属性脱敏为 ******,可放心打日志;cfg.RedactedDSN() 返回脱敏后的 DSN 字符串。注意:脱敏仅覆盖 fmt/Stringer 路径,不要把原始 Config/MySQLConfig 直接传给结构化日志器(如 slog.Any)或用于序列化日志——请改用 String()RedactedDSN()

base := ormx.NewConfig(
    ormx.WithHost("db.internal"),
    ormx.WithUser("app"),
    ormx.WithPassword(os.Getenv("DB_PASSWORD")),
)
orders, err := base.With(ormx.WithDatabase("orders"), ormx.WithName("orders")).Open(ctx)
users, err  := base.With(ormx.WithDatabase("users"), ormx.WithName("users")).Open(ctx)
选项函数
连接与 DSN
Option 默认值 说明
WithName(name) "default" 实例名,用于 Client.Name、健康报告与事务重试事件;多实例时建议显式设置
WithDSN(dsn) 以完整 DSN(如 user:pass@tcp(host:3306)/db?parseTime=true整体替换 MySQL 连接子配置:DSN 未写的参数按驱动默认(parseTime=false、时区 UTC、无超时),不叠加本包默认;本库未单独建模的驱动参数(multiStatementsmaxAllowedPacket、charset 回退列表等)原样透传给驱动,不丢失。建议放在其它连接 Option 之前,之后的 Option 仍可覆盖单个字段(含 WithHost/WithPort)。DSN 仅应来自可信静态配置,不得直接接收用户输入,也不要记录含凭据的原始 DSN(日志用 RedactedDSN)——透传的参数中可能包含影响安全边界的驱动开关
WithHost(host) 127.0.0.1 主机;设置后清空 Addr
WithPort(port) 3306 端口;设置后清空 Addr
WithAddress(addr) 完整地址(host:port),优先级高于 Host/Port
WithNetwork(network) tcp 网络类型;用 unix 时必须配 WithAddress("/path/mysql.sock") 指定 socket 路径,否则 Open 返回 ErrAddressRequired
WithDatabase(name) 数据库名
WithUser(user) 用户名
WithPassword(password) 密码(日志输出自动脱敏)
WithParseTime(enabled) true 是否把 DATETIME 解析为 time.Time
WithLocation(loc) time.Local DSN 时区
WithTimeout(d) 10s 建连超时
WithReadTimeout(d) 30s I/O 读超时
WithWriteTimeout(d) 30s I/O 写超时
WithTLSConfig(name) TLS 配置名:支持驱动内置值 true / false / skip-verify / preferred(无需注册),或经 mysql.RegisterTLSConfig 注册的名称。生产环境推荐 true 或启用证书验证的自定义配置;preferred 可能回退明文连接、skip-verify 不验证服务端证书,仅适合受控环境
WithCharset(charset) 驱动默认(utf8mb4) 显式指定连接字符集,连接后执行 SET NAMES <charset>;配 WithCollation 时执行 SET NAMES <charset> COLLATE <collation>。驱动默认已是 utf8mb4,非必选项;标识符仅允许字母/数字/下划线,仅支持单一字符集(回退列表返回 ErrDSNUnsupported,需要时经 WithDSNcharset= 参数设置)
WithCollation(collation) 驱动默认 连接 collation
WithConnectionAttributes(attrs) 连接属性(performance_schema.session_connect_attrs
WithSystemVariable(key, value) 追加连接系统变量,连接后执行 SET key = value;value 须是合法 SQL 表达式、且仅接受可信静态配置。 DSN 内置参数(locWithLocationparseTimeWithParseTimecharsetWithCharset 等)
WithSystemVariables(params) 批量追加连接系统变量,语义同上
连接池

这四个选项透传到标准库 sql.DB 的对应方法(Open 用下列默认值初始化;OpenWithDB 只应用显式传入的项,其余保持外部 *sql.DB 原样):

Option 默认值 透传到 取值语义
WithMaxOpenConns(n) 50 sql.DB.SetMaxOpenConns n ≤ 0 表示不限制打开连接数
WithMaxIdleConns(n) 10 sql.DB.SetMaxIdleConns n ≤ 0 表示不保留空闲连接
WithConnMaxLifetime(d) 30m sql.DB.SetConnMaxLifetime d ≤ 0 表示连接不过期
WithConnMaxIdleTime(d) 10m sql.DB.SetConnMaxIdleTime d ≤ 0 表示空闲连接不因闲置被关闭
client, err := ormx.Open(ctx,
    ormx.WithHost("127.0.0.1"), ormx.WithDatabase("app"),
    ormx.WithUser("app"), ormx.WithPassword(os.Getenv("DB_PASSWORD")),
    ormx.WithMaxOpenConns(100),
    ormx.WithMaxIdleConns(20),
    ormx.WithConnMaxLifetime(time.Hour),
    ormx.WithConnMaxIdleTime(10*time.Minute),
)

注意:database/sql 会把 MaxIdleConns 自动限制到不超过 MaxOpenConns——调小 MaxOpenConns 时记得同步下调 MaxIdleConns,否则多出的空闲上限会被静默截断。

GORM 行为
Option 默认值 说明
WithGormLogger(log) Discard(静默) 设置任意 gormlogger.Interface 实现;默认静默,不输出任何 SQL 日志
WithZlogger(opts...) 无参为 no-op(静默) 一步注入 zap 日志器,等价 WithGormLogger(zlogger.New(opts...));不传 Option 时用 zlogger 默认(no-op logger,静默丢弃),须至少 zlogger.WithLogger(...) 注入 zap。详见下文 zlogger 章节
WithZapLogger(zlog, opts...) 直传 *zap.Logger 一步接入,等价 WithZlogger(zlogger.WithLogger(zlog), opts...)(接 zap 的最短路径);nil 回退 no-op,附加 zlogger.Option 在其后按序生效
WithPrepareStmt(enabled) false 开启预编译语句缓存。默认关闭;适合长生命周期单例 Client,不要频繁 Open/Close(GORM 的 TTL 缓存清理 goroutine 不随 Close 退出,Close 仅释放已缓存语句)
WithPrepareStmtCache(maxSize, ttl) GORM 默认 预编译语句缓存容量与 TTL,仅在 WithPrepareStmt(true) 时生效;不设置时沿用 GORM 的缓存默认
WithSkipDefaultTransaction(skip) false 跳过 GORM 单条写操作的默认事务
WithNowFunc(fn) time.Now GORM 时间函数(测试注入用)
WithNamingStrategy(strategy) IdentifierMaxLength: 64 整体替换命名策略
WithTablePrefix(prefix) 表名前缀
WithSingularTable(enabled) false 使用单数表名
WithDefaultContextTimeout(d) 0(不限制) GORM 操作默认 context 超时
WithDefaultTransactionTimeout(d) 0(不限制) GORM 事务默认超时
WithDryRun(enabled) false 只生成 SQL 不执行
WithQueryFields(enabled) false SELECT 时展开全部字段名而非 *
WithCreateBatchSize(n) 0 批量插入分批大小
WithTranslateError(enabled) false 把驱动错误翻译为 GORM 错误(如 ErrDuplicatedKey
SQL 日志开关

默认静默:不传 WithGormLogger 时,本库默认注入 gormlogger.Discard,不向 stdout 输出任何 SQL、也不会泄露绑定参数。日志需显式开启,通过 gormlogger.Interface 控制,不需要额外布尔开关:

// 默认即静默;如需显式关闭也可
ormx.WithGormLogger(gormlogger.Discard)

// 记录错误 SQL 与超过 200ms 的慢 SQL;
// 注意 gormlogger.Default 会把真实绑定参数插值进日志,仅用于受控开发环境
ormx.WithGormLogger(gormlogger.Default.LogMode(gormlogger.Warn))

// 记录全部 SQL;同样会输出真实绑定参数,仅用于受控开发环境
ormx.WithGormLogger(gormlogger.Default.LogMode(gormlogger.Info))

生产环境需要结构化日志时,建议使用下文的 zlogger,并开启参数化查询,避免 SQL 绑定参数进入日志。

启动与健康
Option 默认值 说明
WithStartupPing(enabled) true Open 时先 Ping 验证连通性
WithStartupPingRetry(maxRetries, baseWait, maxWait) 0, 1s, 5s 启动 Ping 失败后的重试次数与退避区间
WithHealthProbe(probe) 自定义健康探针,在 Ping 通过后追加执行(如跑一次轻量业务查询确认连接可用)
事务观测
Option 默认值 说明
WithTxRetryObserver(observer) 每次死锁重试前回调 TxRetryEvent(实例名、第几次、等待时长、错误),用于打点告警
MySQL Dialect(少用,对接非标准部署时才需要)
Option 默认值 说明
WithServerVersion(version) 自动探测 手工指定服务端版本,仅在 WithSkipInitializeWithVersion(true) 时生效;否则会被 SELECT VERSION() 结果覆盖。注意跳过版本探测后,GORM 不再据版本自动推导兼容标志
WithSkipInitializeWithVersion(skip) false 跳过按版本初始化
WithDefaultStringSize(size) 0 string 字段默认长度
WithDisableDatetimePrecision(disable) false 禁用 datetime 精度(兼容 MySQL 5.6 以前)
WithDisableWithReturning(disable) false 禁用 RETURNING 子句
Client 方法
方法 说明
DB() *gorm.DB 取 GORM 句柄
SQLDB() *sql.DB 取底层 *sql.DB(可交给 jetx 等共享连接池)
Config() Config 配置的脱敏快照(隔离副本,密码/参数值/连接属性已脱敏,不含明文凭据)
Name() string 实例名(未设置时为 default
PingContext(ctx) error 连通性检查
StatsSnapshot() DBStatsSnapshot 连接池统计快照(额外含 Utilization),业务层据此自行对接监控;需原始 sql.DBStatsSQLDB().Stats()
HealthCheck(ctx) HealthReport 健康检查(Ping + 自定义探针,默认 5s 超时)
Transaction / WithTx / WithReadTx 事务,见下节
Close() error 关闭连接池(OpenWithDB 包装的实例不关闭外部 *sql.DB
事务(死锁自动重试)
err := client.Transaction(ctx, func(tx *gorm.DB) error {
    if err := tx.Create(&order).Error; err != nil {
        return err
    }
    return tx.Model(&stock).Update("count", gorm.Expr("count - ?", 1)).Error
})
  • fn 返回 nil 则提交,返回 error 则回滚;panic 时回滚后继续抛出。
  • 遇到 MySQL 死锁(1213)或锁等待超时(1205)时自动按带抖动的指数退避重试,默认最多 3 次。重试意味着 fn 可能执行多次,事务内逻辑须幂等。
  • 需要指定隔离级别/只读时用 WithTx(ctx, &sql.TxOptions{...}, fn)Transaction 等价于 WithTx(ctx, nil, fn));WithReadTx(ctx, fn)ReadOnly: true 的便捷形式。

每次调用可用 TxOption 覆盖重试行为:

TxOption 默认值 说明
WithMaxRetries(n) 3 最大重试次数,0 禁用重试
WithRetryBaseWait(d) 5ms 退避基础等待
WithRetryMaxWait(d) 50ms 单次退避上限
err := client.Transaction(ctx, fn, ormx.WithMaxRetries(5), ormx.WithRetryMaxWait(200*time.Millisecond))
多库多实例

配置按实例隔离(没有全局状态),每次 Open 返回独立的 *Client,各自持有独立连接池。连接多个库就是创建多个实例,按依赖注入交给各业务模块:

orderDB, err := ormx.Open(ctx, ormx.WithDatabase("orders"), ormx.WithName("orders") /* ... */)
userDB, err  := ormx.Open(ctx, ormx.WithDatabase("users"), ormx.WithName("users") /* ... */)

orderRepo := repo.NewOrderRepo(orderDB.DB())
userRepo  := repo.NewUserRepo(userDB.DB())
健康检查与连接池统计

单机 Client 提供健康检查与连接池统计快照,指标如何暴露(gauge / counter 语义)由业务监控层决定:

report := client.HealthCheck(ctx)
if !report.Healthy() {
    log.Printf("db down: %v", report.Error)
}

s := client.StatsSnapshot()
// gauge 类(当前值):OpenConnections / InUse / Idle / Utilization ...
openConns.Set(float64(s.OpenConnections))
// counter 类(累计值,用 Counter 而非 Gauge):WaitCount / MaxIdleClosed / MaxLifetimeClosed ...
waitCountTotal.Add(float64(s.WaitCount))

WithHealthProbe 可在 Ping 之外追加业务探针,例如执行一次轻量查询确认连接可用:

ormx.WithHealthProbe(func(ctx context.Context, c *ormx.Client) error {
    var one int
    return c.DB().WithContext(ctx).Raw("SELECT 1").Scan(&one).Error
})
错误处理

以下导出哨兵错误可用 errors.Is 判定:

错误 触发场景
ormx.ErrAddressRequired 既未提供 Addr、又缺 Host/Port,或 unix 网络未用 WithAddress 指定 socket 路径
ormx.ErrNilSQLDB OpenWithDB 传入 nil *sql.DB
ormx.ErrNilTxFunc Transaction/WithTx 传入 nil 事务函数
ormx.ErrSystemVariableNameRequired 系统变量名为空或纯空白
ormx.ErrDSNUnsupported DSN 级设置无法经当前 API 表达:WithCharset 传回退列表(utf8mb4,utf8)、用 WithCharset("") 清除来自 WithDSN 的 charset,或 DSN 使用驱动已移除的参数(如 strict
if _, err := ormx.Open(ctx, /* ...缺少地址... */); errors.Is(err, ormx.ErrAddressRequired) {
    // 配置缺少连接地址
}

zlogger(GORM 的 zap 日志适配)

zlogger 是实现 gormlogger.Interface 的 zap 日志器。用 ormx.WithZapLogger 直传 *zap.Logger 一步接入:

import (
    "github.com/gtkit/ormx"
    "github.com/gtkit/ormx/zlogger"
    gormlogger "gorm.io/gorm/logger"
    "go.uber.org/zap"
)

zlog, _ := zap.NewProduction()

client, err := ormx.Open(ctx,
    // ...连接选项...
    ormx.WithZapLogger(zlog,
        zlogger.WithLogLevel(gormlogger.Warn),
        zlogger.WithSlowThreshold(300*time.Millisecond),
        zlogger.WithIgnoreRecordNotFoundError(true),
        zlogger.WithParameterizedQueries(true),
        zlogger.WithTraceIDExtractor(func(ctx context.Context) string {
            if id, ok := ctx.Value("X-Request-ID").(string); ok {
                return id
            }
            return ""
        }),
    ),
)

WithZapLogger(zlog, opts...) 等价于 WithZlogger(zlogger.WithLogger(zlog), opts...),后者(WithZlogger(opts...)WithGormLogger(zlogger.New(opts...)))适合 Option 完全由外部组装的场景。需要注入自定义 gormlogger.Interface 实现(或已构造好的日志器)时,仍用 WithGormLogger

多个数据库共享同一日志器时,建议给各实例的 zap logger 显式附加区分字段:

ormx.WithName("orders"),
ormx.WithZapLogger(zlog.With(zap.String("database", "orders"))),
选项函数
Option 默认值 说明
WithLogger(log) nop(不输出) 底层 *zap.Logger不设置则所有日志静默丢弃,必须传入
WithLogLevel(level) gormlogger.Warn 日志级别(Silent / Error / Warn / Info)
WithSlowThreshold(d) 200ms 慢查询阈值;执行耗时超过即按 Warn 输出 gorm slow query,设为 0 关闭慢查询日志
WithIgnoreRecordNotFoundError(enabled) false 忽略 gorm.ErrRecordNotFound,不作为错误日志输出
WithParameterizedQueries(enabled) true(安全默认) 开启时日志中的 SQL 不带参数值(脱敏),只输出占位符语句;调试需要看真实参数值时显式传 false 关闭
WithTraceIDExtractor(fn) 从 context 提取 trace/request id,附加为 trace_id 字段,串联 SQL 日志与请求链路
输出行为

每条 SQL 日志包含字段:source(调用位置)、elapsed(耗时)、sqlrows(影响行数,-1 时省略)、trace_id(配置了 extractor 且能提取到时)。按以下优先级输出:

  1. 执行出错(且未被 RecordNotFound 忽略)→ Errorgorm query error,附 error 字段
  2. 耗时超过慢查询阈值 → Warngorm slow query,附 slow_threshold 字段
  3. 日志级别为 Info → Infogorm query(全量 SQL 日志,仅建议开发环境开启)

LogMode 遵循 GORM 约定返回调级别后的副本,可配合 db.Session(&gorm.Session{Logger: ...}) 做局部调级。

安全提示:参数化查询默认开启(隐藏绑定参数)。仅在受控排障环境显式传 WithParameterizedQueries(false) 打开真实参数——它会把密码、Token 等敏感值写入日志。


paginator(通用分页查询执行器)

paginator 基于 GORM 执行分页查询:Count 总数、钳制页码与页大小、按稳定排序执行 LIMIT/OFFSET,返回泛型结果。

import "github.com/gtkit/ormx/paginator"

query := client.DB().WithContext(ctx).
    Model(&Topic{}).
    Where("category_id = ?", cid).
    Preload("Comments") // 预加载等查询装配直接在句柄上完成,本包不代理

page, err := paginator.Paginate[Topic](query,
    paginator.Params{Page: 2, PageSize: 20, Sort: "created", Order: "desc"},
    paginator.WithSortMapping(map[string]string{"created": "created_at"}), // 线上推荐
)
// err == nil 时 page.Items 恒非 nil;另含 CurrentPage / TotalPage / TotalCount

核心契约:

  • 三子句全权负责:入参句柄上链式设置的 ORDER BY / LIMIT / OFFSET 会被清除,分页与排序只由 Params 与 Option 决定;链式 WHERE/Joins/Select 等其余条件原样保留。这三个子句以追加在最后的 scope 形式下发,所以写在 Scopes 里的排序与分页(GORM 官方文档正把分页列为 Scopes 的典型用法)会被本包覆盖,而不会反过来截断统计或破坏排序——scope 的 Where 等条件仍照常生效。

  • 嵌套 scope 会在执行前被拦下:GORM 的执行入口是「只要还有 scope 就再跑一轮」的多轮循环,scope 内部再注册 scope(组合式 helper)会排到下一轮、也就是本包之后。gorm.Statement.scopes 未导出、包外无法排空,因此本包通过 StatementModifier 在全部 scope 结束后、SQL 构建前按事实校验,任一项不符即返回 ErrDeferredPaginationClause;不会先执行被取消 LIMIT 的无界查询,也不会用 DryRun 探针重复执行调用方 scope:

    被改动的东西 放行后的静默后果
    LIMIT / OFFSET 被覆盖 统计被截断 → 总数为 0、翻页返回空页
    排序被 Reorder 截断 行序由调用方决定,翻页稳定性失效
    clause.OrderBy.Expression 顶替排序 Build 完全忽略 Columns(实测生成 ORDER BY RAND())→ 翻页重复且遗漏
    scope 里改 Select / Distinct / Group Count 策略与数据投影不一致 → 总数错误或扫描失败
    scope 里注入 Raw GORM 忽略普通分页 Clause → 返回未分页数据

    嵌套 scope 只加条件(Where 等)时不受影响;普通追加排序若不截断本包排序,也会保留在数据查询末尾,但会从 Count 中移除。投影快照覆盖 DistinctSelects、SELECT Clause 与 GROUP BY Clause;请把 Select/Distinct/Group 写在链式调用上。

  • 入参句柄零污染:内部先把 Statement 私有化再工作,调用后原句柄可安全复用;多个 goroutine 并发把同一句柄传给 Paginate 也是安全的(-race 覆盖)。但该句柄同时被用于其它查询(Find/First/Count 等)不安全——这是 GORM 句柄语义所限,请各 goroutine 自行 Session/WithContext 派生。

  • 结构化排序,不拼原始 SQL:排序经 GORM 的 clause.OrderBy 下发,列名由方言引擎加引号——保留字列(如 order)可安全排序。列名文法校验(点分 1~2 段、每段合法标识符,拒绝纯数字位置排序与空段)作为第二道防线。

  • 自动表名限定:排序列与主键列在表名可判定时自动加表名前缀,消除 Join 场景的列歧义。可判定:纯 Model 查询、Table("t")Table("db.t")Table("t AS a")Table("t a")Table("(SELECT …) AS a")(带别名时用别名)。不可判定Table 表达式含 JOIN/多表(GORM 提取不出别名,回退基表名会生成 FROM 里不存在的限定名 → Error 1054),此时一律不限定,排序列需调用方自行限定;自动追加的主键次级排序同样会是裸列名——多张表都有同名主键列时数据库会报列歧义(MySQL Error 1052,响亮失败)。这类查询请改用 Model + Joins(schema 表名可判定,主键会被限定),或经 WithDefaultSort 指定已限定的排序列并确认主键列名在该查询里不歧义。别名与计算列(不属模型字段)也保持未限定。

  • 排序稳定性:默认按模型主键排序(自定义主键、复合主键自动识别全集);指定其他排序列时自动追加全部主键列作次级排序,方向跟随主排序,同列不同写法自动去重。在数据集不变且排序键组合唯一时翻页不重不漏。

  • PageSize 有上限(默认 100):该参数常来自远端请求,钳制以防无界查询。任意配置值(含 math.MaxInt)与任意总数(含 math.MaxInt64)下均不 panic、分页元信息不溢出。

  • 入参错误不吞:句柄携带的 db.Error 在入口即被包装返回,任何路径(含 WithTotal 短路)都不会静默成功;缺少 Config、Dialector、ConnPool、Statement、Context 等 gorm.Open 初始化状态的手工句柄统一返回 ErrNilDB;模型不可解析时透传 parse model 根因而非伪装成排序错误。

  • 不接受 Raw 查询句柄db.Raw(...) 会预先填充 Statement.SQL,GORM 随后不会重建 ORDER BY / LIMIT / OFFSET;本包无法可靠改写,故执行前返回 ErrDeferredPaginationClause。Raw SQL 请在分页器外自行分页,或改用 Model/Table 查询构建器。

参数 行为
Page 从 1 开始,越界钳制到 [1, 总页数];无数据时 CurrentPage/TotalPage 为 0
PageSize <=0 取默认 10;上限默认 100(WithMaxPageSize 调整)
Sort WithSortMapping 时仅接受映射键(未命中回退默认排序);未设映射时经列名文法校验,非法回退默认排序
Order 仅 asc/desc(不区分大小写),其余回退 asc

Option(只配置分页器自身,nil 安全跳过):

Option 说明
WithMaxPageSize(n) PageSize 上限,默认 100。上限本身是信任边界配置:调大即接受对应查询开销
WithDefaultSort(col) 覆盖默认排序列(默认模型主键)
WithSortMapping(m) 外部排序键→受信任列的显式映射:防注入之余限制可排序列集合(防无索引大排序),线上推荐。m 不得为 nil(零键 allowlist 传空 map);构造时做防御性复制,之后修改原 map 不影响行为
WithTotal(n) 调用方提供总数并跳过 count。所有 DISTINCT 查询均为必需项;GROUP BY 可自动统计(高基数分组建议仍用本项,见下)
受限投影(DISTINCT / GROUP BY)

这两类查询的投影与分组决定了哪些列可出现在 ORDER BY(MySQL 的 DISTINCT 约束与 ONLY_FULL_GROUP_BY),自动主键排序会被数据库拒绝。本包不猜测调用方的投影,改为收窄契约 + fail fast

page, err := paginator.Paginate[Topic](
    db.Model(&Topic{}).Select("category_id, COUNT(*) AS c").Group("category_id"),
    paginator.Params{Page: 1, PageSize: 20, Sort: "category_id"}, // 必须显式排序
) // 总数自动统计为分组数量
  • 未提供显式排序列 → 返回 ErrSortRequired,且不发出任何 SQL
  • 提供后该排序列不做表名限定、不追加主键次级排序,与投影的兼容性由调用方保证。
  • 受限投影当前只能表达一个排序列;该列或别名必须能唯一确定结果行。多列 GROUP BY 若不存在唯一单列排序键,不适合使用本分页器,否则相同排序值的行序不稳定。

统计(Count)语义按去重写法区分,以真实 MySQL 8 实测为准:

写法 GORM Count 生成 总数 本包行为
单列 .Distinct("col") count(DISTINCT col) 排除 NULL,可能比 SELECT DISTINCT col 少 1 返回 ErrTotalRequired
.Group("col") / clause.GroupBy count(*) … GROUP BY,取返回行数(*count = tx.RowsAffected 分组数量,准确 自动统计
多列 .Distinct("a","b") / .Distinct().Select("a, b") count(*) 总行数,非去重数 返回 ErrTotalRequired
Clauses(clause.Select{Distinct: true}) count(*) 总行数,非去重数 返回 ErrTotalRequired
原始字符串 Select("DISTINCT …") / Select("DISTINCTROW …") count(*) 总行数,非去重数 返回 ErrTotalRequired

即使 GORM 生成了 count(DISTINCT col),它也不能证明分页总数可靠:MySQL 的 Count 排除 NULL,而数据查询会保留一个 NULL。实测可空列有 5 个非空去重值和 1 个 NULL 时,Count 返回 5、数据投影返回 6。多列 Distinct("name", "order") 又会退化为 count(*)(25 行 / 15 个去重组合时返回 25)。因此本包对所有 DISTINCT 统一 fail-closed,必须传入准确的 WithTotal

GROUP BY 自动统计的代价:统计查询每个分组返回一行,开销为 O(分组数) 的网络传输与扫描。高基数分组(几十万以上)请改用 WithTotal 自行统计。

写在 Scopes 里的 Select/Distinct/Group 会在 SQL 执行前被投影快照拦下(返回 ErrDeferredPaginationClause,见上表)。原始字符串前的空白、/* ... */-- ...# ... 注释及优化器 hint 会先被跳过,再识别 DISTINCT/DISTINCTROW

其他使用限制(如实声明)
  • 非受限投影下 Count 遵循 GORM Count 语义:入参含 Select/Joins 时"总数"含义随之变化(COUNT(col) 跳过 NULL、has-many Join 重复计数);不符合需求时用 WithTotal
  • Count 与数据查询是两条 SQL,非一致性快照;严格一致场景请传入事务内句柄。
  • 调用方 Scope 必须是确定且无副作用的查询装配函数:自动 Count 时同一 Scope 执行 2 次(Count、数据各一次),WithTotal(n>0) 时执行 1 次WithTotal(0) 时执行 0 次time.Now()、随机值、递增计数器或一次性迭代器应在调用 Paginate 前求值并捕获;事务不能修复两次 Scope 生成了不同条件的问题。Scope 只能使用 GORM 公开链式 API 返回查询句柄,不得直接改写 Statement.DestStatement.SQLStatement.Clauses 等内部状态,也不得在 Scope 内执行查询。
  • 自定义 GORM Query callback 不得在 StatementModifier 之后改写 ORDER BY、LIMIT、OFFSET 或投影;这类全局 callback 超出单次查询守卫的控制范围。仓库内置代码未注册此类 callback,下游接入 GORM 插件时需自行确认。
  • 仅 OFFSET 分页:深分页(大数据量 × 大页码)不适合高频接口,此类场景应采用游标分页(不属于本包 API)。
  • WithTotal 传入的总数不与实际数据核对:值不准(缓存陈旧、算错)时会静默产出错误页数与空页,准确性由调用方负责。
  • 行为仅在 MySQL 8 上做过真实数据库验证。其他方言的引号与 DISTINCT/GROUP BY 宽容度不同(SQLite 对 ORDER BY 不在选择列表中更宽容、Postgres 加引号后大小写敏感),使用前请自行验证。
测试

真实 MySQL 集成测试是发布前置条件make tag 强制执行)——fake driver 只能验 SQL 形状,DISTINCT / ONLY_FULL_GROUP_BY / 保留字 / Join 同名列这几类只有真实数据库能判定:

make test-integration                                   # 默认 root:@tcp(127.0.0.1:3306)/
make test-integration ORM_TEST_DSN='user:pass@tcp(host:3306)/'

Documentation

Overview

Package ormx 提供基于 GORM 的 MySQL 数据访问便捷封装: 连接与连接池配置、事务死锁自动重试、单机连接健康探活与可观测,以及 GORM 的 zap 日志适配。

基本用法:

client, err := ormx.Open(ctx,
    ormx.WithHost("127.0.0.1"),
    ormx.WithPort("3306"),
    ormx.WithDatabase("app"),
    ormx.WithUser("root"),
    ormx.WithPassword(os.Getenv("DB_PASSWORD")),
)
if err != nil {
    return err
}
defer client.Close()

db := client.DB() // *gorm.DB,直接走 GORM API

事务通过 Client.Transaction(或需要 *sql.TxOptions 时的 Client.WithTx)执行, 遇 MySQL 死锁(1213)或锁等待超时(1205)自动按带抖动的指数退避重试。

连接健康检查与连接池统计见 Client.HealthCheckClient.StatsSnapshot

Client 可在多个 goroutine 间并发使用;调用方注入的 logger、HealthProbe、 TxRetryObserver 的并发安全由调用方保证。 GORM 的 zap 日志适配见子包 zlogger;通用分页查询执行器见子包 paginator。

Index

Examples

Constants

View Source
const Version = "v1.5.0"

Version 是 ormx 当前发布的版本号。

Variables

View Source
var ErrAddressRequired = errors.New("ormx: mysql address is required")

ErrAddressRequired 表示缺少连接地址:既未提供 Addr、又未同时提供 Host 与 Port, 或使用 unix 网络(Net=="unix")时未用 WithAddress 指定 socket 路径。可用 errors.Is 判定。

View Source
var ErrDSNUnsupported = errors.New("ormx: dsn settings not supported")

ErrDSNUnsupported 表示 DSN 级设置无法经当前 API 表达:WithCharset 传入 charset 回退列表 (如 "utf8mb4,utf8",回退列表仅能经 WithDSN 的 charset 参数设置)、试图用 WithCharset("") 清除来自 WithDSN 的 charset,或 DSN 使用驱动已移除的参数(如 strict)。可用 errors.Is 判定。

View Source
var ErrNilSQLDB = errors.New("ormx: nil *sql.DB")

ErrNilSQLDB 表示向 OpenWithDB 传入了 nil *sql.DB;可用 errors.Is 判定。

View Source
var ErrNilTxFunc = errors.New("ormx: nil transaction function")

ErrNilTxFunc 表示向 WithTx 传入了 nil 事务函数;可用 errors.Is 判定。

View Source
var ErrSystemVariableNameRequired = errors.New("ormx: system variable name must not be empty")

ErrSystemVariableNameRequired 表示系统变量名为空或纯空白字符;可用 errors.Is 判定。

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client 封装单个数据库连接,持有 GORM 实例与底层 *sql.DB。 Client 自身状态只读,可在多个 goroutine 间并发使用;但调用方注入的 Logger、HealthProbe、TxRetryObserver 会在并发下被调用,其并发安全由调用方保证。

func MustOpen

func MustOpen(ctx context.Context, opts ...Option) *Client

MustOpen 以 NewConfig(opts...) 构建配置并调用 Config.MustOpen,失败时 panic。

func Open

func Open(ctx context.Context, opts ...Option) (*Client, error)

Open 以 NewConfig(opts...) 构建配置并调用 Config.Open,是最常用的入口函数。

func OpenWithDB

func OpenWithDB(ctx context.Context, sqlDB *sql.DB, opts ...Option) (*Client, error)

OpenWithDB 包装既有的 *sql.DB:只把 opts 中显式传入的连接池参数应用到 sqlDB, 不强加包内默认(借用的外部连接池由调用方自行配置,未显式覆盖的项保持不变)。 成功打开后 Client.Close 不会关闭该外部 *sql.DB;但注意:若 GORM 初始化失败, GORM 的清理逻辑可能关闭传入的 *sql.DB,失败后请勿再复用它(见 Config.OpenWithDB)。

func (*Client) Close

func (c *Client) Close() error

Close 先释放 GORM 预编译语句缓存(启用 WithPrepareStmt 时;GORM 以异步方式关闭已缓存语句), 再关闭底层 *sql.DB——仅当 Client 拥有该连接时才真正关闭,否则(OpenWithDB 场景)不关闭外部 DB。

func (*Client) Config

func (c *Client) Config() Config

Config 返回客户端配置的脱敏快照:深拷贝后把密码、连接参数值与连接属性替换为占位符, 不含明文凭据或敏感绑定值,仅供检视。需要真实值请由调用方保留原始配置。

func (*Client) DB

func (c *Client) DB() *gorm.DB

DB 返回底层 *gorm.DB。

func (*Client) HealthCheck

func (c *Client) HealthCheck(ctx context.Context) HealthReport

HealthCheck 执行一次健康检查(Ping 加可选的 HealthProbe)并返回报告。 当 ctx 未设置 deadline 时使用内置默认超时,避免无限阻塞。

func (*Client) Name

func (c *Client) Name() string

Name 返回客户端名称,未在 Config 中配置时返回 "default"。

func (*Client) PingContext

func (c *Client) PingContext(ctx context.Context) error

PingContext 检测数据库连接是否可用。

func (*Client) SQLDB

func (c *Client) SQLDB() *sql.DB

SQLDB 返回底层 *sql.DB。

func (*Client) StatsSnapshot

func (c *Client) StatsSnapshot() DBStatsSnapshot

StatsSnapshot 返回当前连接池统计信息的快照。

func (*Client) Transaction added in v1.3.0

func (c *Client) Transaction(ctx context.Context, fn func(tx *gorm.DB) error, txOpts ...TxOption) error

Transaction 在事务中执行 fn,等价于 WithTx(ctx, nil, fn, txOpts...), 是最常见调用(默认事务选项)的便捷入口:提交/回滚、死锁自动重试与 panic 回滚后原样上抛的语义均见 WithTx。需要指定 *sql.TxOptions (隔离级别、只读)时用 WithTx;只读事务另有便捷入口 WithReadTx。

Example

Transaction 是最常见事务调用的便捷入口:fn 返回 nil 提交、返回 error 回滚, 遇 MySQL 死锁/锁等待超时自动重试(示例用进程内 stub 数据库,可执行验证)。

sqlDB, _ := newStubDB()
defer sqlDB.Close()

client, err := OpenWithDB(context.Background(), sqlDB,
	WithStartupPing(false), WithSkipInitializeWithVersion(true))
if err != nil {
	fmt.Println("open:", err)
	return
}
defer client.Close()

txErr := client.Transaction(context.Background(), func(tx *gorm.DB) error {
	// 在事务中执行业务操作;返回 nil 则提交,返回 error 则回滚。
	return tx.Exec("UPDATE widgets SET active = 1").Error
})
fmt.Println(txErr == nil)
Output:
true

func (*Client) WithReadTx

func (c *Client) WithReadTx(ctx context.Context, fn func(tx *gorm.DB) error) error

WithReadTx 在只读事务中执行 fn,重试行为与 WithTx 的默认值一致。

func (*Client) WithTx

func (c *Client) WithTx(
	ctx context.Context, opts *sql.TxOptions, fn func(tx *gorm.DB) error, txOpts ...TxOption,
) error

WithTx 在事务中执行 fn:fn 返回 nil 则提交,返回 error 则回滚。 遇到 MySQL 死锁(1213)或锁等待超时(1205)时按带抖动的指数退避自动重试, 重试行为可通过 TxOption 调整;fn 为 nil 时返回错误。 由于会重试,fn 可能被多次调用,必须可重入且幂等(不要依赖闭包外的一次性副作用)。 若 fn 发生 panic,事务会先回滚,随后 panic 继续向上传播(不被吞没为 error), 以保留调用方自身的 panic 处理语义。

type Config

type Config struct {
	Name                     string
	MySQL                    MySQLConfig
	Pool                     PoolConfig
	GORM                     GORMConfig
	Dialect                  MySQLDialectConfig
	HealthProbe              HealthProbeFunc
	TxRetryObserver          TxRetryObserver
	StartupPing              bool
	StartupPingMaxRetries    int
	StartupPingRetryBaseWait time.Duration
	StartupPingRetryMaxWait  time.Duration
	// contains filtered or unexported fields
}

Config 汇总建立 MySQL 连接所需的全部配置:驱动连接参数(MySQL)、 连接池(Pool)、GORM 行为(GORM)、方言(Dialect)以及启动期 Ping 重试策略。 Config 通过 With / Clone 返回隔离副本(仅复制包内可变字段),不修改原值。注意普通赋值(cfg2 := cfg) 只是浅拷贝,仍与原值共享 SystemVariables map 与连接池等指针字段;需要独立副本时用 Clone 或 With。 Config 是运行期配置,推荐用 Option(Open / NewConfig / With)构建;直接改字段会绕过 Option 的防御逻辑, 合法性由调用方自行保证。它不承诺可整体序列化——仅 MySQL、Pool 带 JSON/YAML 标签, 而 Logger、HealthProbe、TxRetryObserver、NowFunc、NamingStrategy 等运行时字段无法从配置文件映射; 配置文件驱动的场景建议由业务侧维护自己的 DTO,再转换成 ormx.Option。 注意:零值 Config 不携带任何默认值——需要默认值时以 DefaultConfig()(或 NewConfig)的返回值为基底再覆盖字段。 Pool 各字段为指针,nil 表示不设置、保持 database/sql 默认。

func DefaultConfig

func DefaultConfig() Config

DefaultConfig 返回带合理默认值的 Config: MySQL 默认通过 tcp 连接 127.0.0.1:3306,时区为 time.Local,启用 ParseTime, 并设置拨号/读/写超时;连接池四项参数均设为包内默认值; GORM 使用默认命名策略;StartupPing 默认开启,重试基础等待 1 秒、上限 5 秒、 默认不重试(StartupPingMaxRetries 为 0)。

func NewConfig

func NewConfig(opts ...Option) Config

NewConfig 在 DefaultConfig 的基础上依次应用 opts 并返回结果。

Example

用 Functional Options 构建配置,并通过 RedactedDSN 输出密码脱敏后的 DSN(可安全打印到日志)。实际连库使用 cfg.Open(ctx) 或包级 ormx.Open。

package main

import (
	"fmt"

	"github.com/gtkit/ormx"
)

func main() {
	cfg := ormx.NewConfig(
		ormx.WithUser("alice"),
		ormx.WithPassword("secret"),
		ormx.WithDatabase("app"),
	)

	dsn, err := cfg.RedactedDSN()
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	fmt.Println(dsn)
}
Output:
alice:******@tcp(127.0.0.1:3306)/app?loc=Local&parseTime=true&readTimeout=30s&timeout=10s&writeTimeout=30s

func (Config) Clone

func (c Config) Clone() Config

Clone 隔离复制包内可变的配置字段:MySQL.SystemVariables 映射、连接池与方言中的可选指针字段, 使副本与原值互不影响。注意它不深拷贝调用方注入的引用型字段—— GORM.Logger、HealthProbe、TxRetryObserver、NamingStrategy.NameReplacer 以及 MySQL.Loc(*time.Location,按不可变共享)仍与原值共享同一实例; WithDSN 的内部解析状态解析后只读,同样按不可变共享。

func (Config) GoString

func (c Config) GoString() string

GoString 实现 fmt.GoStringer,使 %#v 输出同样脱敏密码。

func (Config) MustOpen

func (c Config) MustOpen(ctx context.Context) *Client

MustOpen 与 Open 行为一致,但在失败时直接 panic,适用于初始化阶段必须成功的场景。

func (Config) Open

func (c Config) Open(ctx context.Context) (*Client, error)

Open 按当前配置构建 MySQL 连接器并打开 *sql.DB,应用连接池配置后初始化 GORM, 返回拥有该 *sql.DB 所有权的 Client(Close 时会一并关闭)。 若 StartupPing 开启,会先按重试策略 Ping 数据库;任一步骤失败时关闭已打开的连接并返回错误。 注意:ctx 仅约束启动 Ping;GORM 初始化时的 SELECT VERSION() 探测由驱动以内部 context.Background() 执行,受连接读超时(WithReadTimeout)约束而非 ctx。若需严格超时, 设置合理的 ReadTimeout,或用 WithSkipInitializeWithVersion + WithServerVersion 跳过该探测。

func (Config) OpenWithDB

func (c Config) OpenWithDB(ctx context.Context, sqlDB *sql.DB) (*Client, error)

OpenWithDB 包装既有的 *sql.DB:GORM 初始化前会把 Config.Pool 中已设置的连接池参数应用到 sqlDB。 打开成功后 Client.Close 不会关闭该外部 *sql.DB(所有权归调用方)。 但注意:若 GORM 初始化(方言初始化/版本探测 SELECT VERSION())失败,GORM 会调用 sqlDB.Close() 清理, 因此打开失败后不应再复用传入的 *sql.DB——这是 GORM 的行为,本库无法在不引入包装层的前提下规避。 另注意:本方法不修改 Config.MySQL,而外部 *sql.DB 的真实连接地址由调用方决定, 因此返回 Client 的 Config().MySQL 未必反映其真实 DSN。

func (Config) RedactedDSN

func (c Config) RedactedDSN() (string, error)

RedactedDSN 返回敏感值脱敏后的 DSN 字符串,可安全用于日志输出; 底层驱动配置构建失败时返回错误。为避免泄露,密码、全部连接系统变量(SystemVariables)值 与连接属性(ConnectionAttributes)在非空时统一替换为 "******",仅保留变量名等结构信息。

func (Config) String

func (c Config) String() string

String 返回敏感值(密码、连接参数值、连接属性)已脱敏的可读表示, 底层复用 RedactedDSN,防止经 fmt.Print / 日志输出意外泄露。

func (Config) With

func (c Config) With(opts ...Option) Config

With 返回应用 opts 后的 Config 副本,原 Config 不受影响;nil Option 会被跳过。

Example

With 返回应用新 Option 后的隔离副本,原配置不受影响 (普通赋值 cfg2 := cfg 只是浅拷贝、仍共享 map 与指针字段,隔离请用 With/Clone)。

package main

import (
	"fmt"

	"github.com/gtkit/ormx"
)

func main() {
	base := ormx.NewConfig(ormx.WithName("base"))
	derived := base.With(ormx.WithName("derived"))

	fmt.Println(base.Name, derived.Name)
}
Output:
base derived

type DBStatsSnapshot

type DBStatsSnapshot struct {
	MaxOpenConnections int
	OpenConnections    int
	InUse              int
	Idle               int
	WaitCount          int64
	WaitDuration       time.Duration
	MaxIdleClosed      int64
	MaxIdleTimeClosed  int64
	MaxLifetimeClosed  int64
	Utilization        float64
}

DBStatsSnapshot 是 sql.DBStats 的快照,并附带连接利用率 Utilization (InUse / MaxOpenConnections,MaxOpenConnections 为 0 时取 0)。

type GORMConfig

type GORMConfig struct {
	Logger                                   gormlogger.Interface
	NowFunc                                  func() time.Time
	NamingStrategy                           schema.NamingStrategy
	DefaultTransactionTimeout                time.Duration
	DefaultContextTimeout                    time.Duration
	PrepareStmt                              bool
	PrepareStmtMaxSize                       int
	PrepareStmtTTL                           time.Duration
	SkipDefaultTransaction                   bool
	DisableForeignKeyConstraintWhenMigrating bool
	IgnoreRelationshipsWhenMigrating         bool
	DisableNestedTransaction                 bool
	AllowGlobalUpdate                        bool
	QueryFields                              bool
	CreateBatchSize                          int
	TranslateError                           bool
	PropagateUnscoped                        bool
	DryRun                                   bool
}

GORMConfig 描述透传给 gorm.Config 的行为配置, 字段与 gorm.Config 中的同名字段一一对应。

type HealthProbeFunc

type HealthProbeFunc func(ctx context.Context, client *Client) error

HealthProbeFunc 是自定义健康探测函数,在 Ping 成功后执行额外检查,返回非 nil 错误表示连接不健康。

type HealthReport

type HealthReport struct {
	Name      string
	Status    HealthStatus
	CheckedAt time.Time
	Duration  time.Duration
	Error     error
	Stats     DBStatsSnapshot
}

HealthReport 描述一次健康检查的结果。

func (HealthReport) Healthy

func (r HealthReport) Healthy() bool

Healthy 报告本次检查状态是否为 HealthStatusUp。

type HealthStatus

type HealthStatus string

HealthStatus 表示健康检查结果的状态。

const (
	HealthStatusUp   HealthStatus = "up"
	HealthStatusDown HealthStatus = "down"
)

健康状态(HealthStatus)的预定义枚举值。

type MySQLConfig

type MySQLConfig struct {
	User                 string            `json:"user"                  yaml:"user"`
	Password             string            `json:"-"                     yaml:"-"`
	Net                  string            `json:"net"                   yaml:"net"`
	Host                 string            `json:"host"                  yaml:"host"`
	Port                 string            `json:"port"                  yaml:"port"`
	Addr                 string            `json:"addr"                  yaml:"addr"`
	Database             string            `json:"database"              yaml:"database"`
	SystemVariables      map[string]string `json:"system_variables"      yaml:"system_variables"`
	ConnectionAttributes string            `json:"connection_attributes" yaml:"connection_attributes"`
	Charset              string            `json:"charset"               yaml:"charset"`
	Collation            string            `json:"collation"             yaml:"collation"`
	Loc                  *time.Location    `json:"-"                     yaml:"-"`
	TLSConfig            string            `json:"tls_config"            yaml:"tls_config"`
	Timeout              time.Duration     `json:"timeout"               yaml:"timeout"`
	ReadTimeout          time.Duration     `json:"read_timeout"          yaml:"read_timeout"`
	WriteTimeout         time.Duration     `json:"write_timeout"         yaml:"write_timeout"`
	ParseTime            bool              `json:"parse_time"            yaml:"parse_time"`
}

MySQLConfig 描述驱动层连接设置。 Addr 与 Host/Port 同时设置时 Addr 优先。 Charset 为单一连接字符集(如 "utf8mb4"),不支持逗号分隔的回退列表(见 WithCharset)。 建议通过 Option 辅助函数设置,以保证 Addr/Host/Port 的优先级语义一致。

func (MySQLConfig) GoString added in v1.2.0

func (c MySQLConfig) GoString() string

GoString 实现 fmt.GoStringer,使 %#v 输出同样脱敏。

func (MySQLConfig) String added in v1.2.0

func (c MySQLConfig) String() string

String 返回敏感值脱敏后的 MySQLConfig 表示,使 fmt 的 %v/%+v/%s 打印 不泄露密码、连接参数值与连接属性;用于安全日志输出。 注意:脱敏仅覆盖 fmt/Stringer 路径,结构化日志器(如 slog.Any)会反射字段、绕过本方法, 因此请勿将原始 Config/MySQLConfig 直接传给结构化日志,改用 String() 或 RedactedDSN()。

type MySQLDialectConfig

type MySQLDialectConfig struct {
	ServerVersion             string
	DefaultStringSize         uint
	DefaultDatetimePrecision  *int
	SkipInitializeWithVersion bool
	DisableWithReturning      bool
	DisableDatetimePrecision  bool
}

MySQLDialectConfig 描述透传给 GORM MySQL 方言(gorm.io/driver/mysql)的配置, 字段与其 Config 中的同名字段一一对应。

type Option

type Option func(*Config)

Option 是修改 Config 的函数式配置项,配合 NewConfig、Open 等入口使用。

func WithAddress

func WithAddress(addr string) Option

WithAddress 设置完整连接地址(如 "127.0.0.1:3306"); Addr 非空时优先于 Host/Port 生效。

func WithCharset added in v1.3.0

func WithCharset(charset string) Option

WithCharset 设置连接字符集(如 "utf8mb4"):连接建立后驱动执行 `SET NAMES <charset>`, 与 WithCollation 同时设置时执行 `SET NAMES <charset> COLLATE <collation>`。 驱动默认连接字符集已是 utf8mb4(默认 collation utf8mb4_general_ci),并非必须设置, 仅在需要显式指定 charset 或与 WithCollation 联用时使用;显式设置会让每条新连接 多执行一次 SET NAMES。

仅支持单一字符集,且标识符只允许字母、数字与下划线(charset 拼入原始 SQL,库层校验 以降低误配置与注入风险)。逗号分隔的回退列表(如 "utf8mb4,utf8")无法经本 Option 设置(需要时用 WithDSN 的 charset 参数),传入会在 Open 时返回包装 ErrDSNUnsupported 的错误;同理,来自 WithDSN 的 charset 无法用 WithCharset("") 清除。

Example

WithCharset 一行设置连接字符集,连接建立后驱动执行 SET NAMES <charset> (配 WithCollation 时执行 SET NAMES <charset> COLLATE <collation>)。

package main

import (
	"fmt"

	"github.com/gtkit/ormx"
)

func main() {
	cfg := ormx.NewConfig(
		ormx.WithUser("alice"),
		ormx.WithPassword("secret"),
		ormx.WithDatabase("app"),
		ormx.WithCharset("utf8mb4"),
	)

	dsn, err := cfg.RedactedDSN()
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	fmt.Println(dsn)
}
Output:
alice:******@tcp(127.0.0.1:3306)/app?charset=utf8mb4&loc=Local&parseTime=true&readTimeout=30s&timeout=10s&writeTimeout=30s

func WithCollation

func WithCollation(collation string) Option

WithCollation 设置连接使用的字符集校对规则。

func WithConnMaxIdleTime

func WithConnMaxIdleTime(duration time.Duration) Option

WithConnMaxIdleTime 设置连接最长空闲时间。默认 10 分钟。 取值透传给 sql.DB.SetConnMaxIdleTime:duration ≤ 0 表示空闲连接不因闲置被关闭。

func WithConnMaxLifetime

func WithConnMaxLifetime(duration time.Duration) Option

WithConnMaxLifetime 设置连接可被复用的最长时间。默认 30 分钟。 取值透传给 sql.DB.SetConnMaxLifetime:duration ≤ 0 表示连接不过期。

func WithConnectionAttributes

func WithConnectionAttributes(attrs string) Option

WithConnectionAttributes 设置 MySQL 连接属性(connection attributes)字符串。

func WithCreateBatchSize

func WithCreateBatchSize(size int) Option

WithCreateBatchSize 设置批量插入时的默认分批大小。

func WithDSN added in v1.3.0

func WithDSN(dsn string) Option

WithDSN 以完整 MySQL DSN(如 "user:pass@tcp(host:3306)/db?parseTime=true")初始化 连接配置,适合配置里已有现成 DSN 的场景。本库未单独建模的驱动参数(multiStatements、 maxAllowedPacket、charset 回退列表等)会原样保留并透传给驱动,连接行为与直接使用该 DSN 一致。注意语义:

  • 整体替换:MySQL 连接子配置以该 DSN 为准,DSN 未写的参数按驱动默认生效 (如 parseTime=false、时区 UTC、无超时),本包 DefaultConfig 的 MySQL 默认不再叠加; 连接池、GORM 行为等非 DSN 配置不受影响。
  • 可继续覆盖:建议把 WithDSN 放在其它连接 Option 之前——之后的 Option 仍可按序 覆盖单个字段(TCP 地址已同步拆出 Host/Port,WithHost/WithPort 覆盖可用)。
  • 失败不 panic:DSN 解析失败(含驱动已移除的 strict 等参数,此类可用 errors.Is(err, ErrDSNUnsupported) 判定)会保留到 Open/RedactedDSN 时报错, 即便后续 Option 覆盖了字段。

安全边界:DSN 仅应来自可信静态配置,不得直接接收用户输入,也不要记录包含凭据的 原始 DSN(日志用 RedactedDSN);未建模的驱动参数会被原样透传,包括可能影响安全 边界的驱动开关(如 allowCleartextPasswords、tls=skip-verify)。

Example

WithDSN 直接以完整 DSN 初始化连接配置(整体替换 MySQL 子配置, DSN 未写的参数按驱动默认),后续 Option 仍可覆盖单个字段。

package main

import (
	"fmt"

	"github.com/gtkit/ormx"
)

func main() {
	cfg := ormx.NewConfig(
		ormx.WithDSN("alice:secret@tcp(db.internal:3307)/app?charset=utf8mb4&parseTime=true"),
		ormx.WithName("orders"),
	)

	dsn, err := cfg.RedactedDSN()
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	fmt.Println(dsn)
}
Output:
alice:******@tcp(db.internal:3307)/app?charset=utf8mb4&parseTime=true

func WithDatabase

func WithDatabase(name string) Option

WithDatabase 设置要连接的数据库名。

func WithDefaultContextTimeout

func WithDefaultContextTimeout(timeout time.Duration) Option

WithDefaultContextTimeout 设置 GORM 操作的默认 context 超时时间。

func WithDefaultStringSize

func WithDefaultStringSize(size uint) Option

WithDefaultStringSize 设置 string 类型字段建表时的默认长度。

func WithDefaultTransactionTimeout

func WithDefaultTransactionTimeout(timeout time.Duration) Option

WithDefaultTransactionTimeout 设置 GORM 事务的默认超时时间。

func WithDisableDatetimePrecision

func WithDisableDatetimePrecision(disable bool) Option

WithDisableDatetimePrecision 设置是否禁用 datetime 字段的精度支持。

func WithDisableWithReturning

func WithDisableWithReturning(disable bool) Option

WithDisableWithReturning 设置是否禁用方言的 RETURNING 子句支持。

func WithDryRun

func WithDryRun(enabled bool) Option

WithDryRun 设置是否启用 DryRun 模式:只生成 SQL 而不真正执行。

func WithGormLogger

func WithGormLogger(log gormlogger.Interface) Option

WithGormLogger 设置 GORM 使用的日志实现。

func WithHealthProbe

func WithHealthProbe(probe HealthProbeFunc) Option

WithHealthProbe 设置自定义健康探针;健康检查在 Ping 成功后调用该探针, 探针返回错误则判定为不健康。

func WithHost

func WithHost(host string) Option

WithHost 设置主机地址,并同时清空 Addr 以保证 Host/Port 生效。默认 "127.0.0.1"。

func WithLocation

func WithLocation(loc *time.Location) Option

WithLocation 设置解析时间值使用的时区。默认 time.Local。 传入 nil 会被忽略并保留原值,避免覆盖默认后在时间解析时因 nil Location 触发 panic。

func WithMaxIdleConns

func WithMaxIdleConns(size int) Option

WithMaxIdleConns 设置连接池最大空闲连接数。默认 10。 取值透传给 sql.DB.SetMaxIdleConns:size ≤ 0 表示不保留空闲连接。

func WithMaxOpenConns

func WithMaxOpenConns(size int) Option

WithMaxOpenConns 设置连接池最大打开连接数。默认 50。 取值透传给 sql.DB.SetMaxOpenConns:size ≤ 0 表示不限制。

func WithName

func WithName(name string) Option

WithName 设置数据库实例名称,用于 Client.Name、健康报告与事务重试事件。

func WithNamingStrategy

func WithNamingStrategy(strategy schema.NamingStrategy) Option

WithNamingStrategy 整体替换 GORM 的命名策略,会覆盖之前设置的表前缀等字段。

func WithNetwork

func WithNetwork(network string) Option

WithNetwork 设置连接 MySQL 使用的网络类型(如 "tcp"、"unix")。默认 "tcp"。 使用 "unix" 时必须配合 WithAddress 指定 socket 路径,否则 Open 返回 ErrAddressRequired。

func WithNowFunc

func WithNowFunc(now func() time.Time) Option

WithNowFunc 设置 GORM 生成时间戳时使用的当前时间函数。

func WithParseTime

func WithParseTime(enabled bool) Option

WithParseTime 设置是否将 DATE/DATETIME 列解析为 time.Time。默认开启。

func WithPassword

func WithPassword(password string) Option

WithPassword 设置连接密码。

func WithPort

func WithPort(port string) Option

WithPort 设置端口,并同时清空 Addr 以保证 Host/Port 生效。默认 "3306"。

func WithPrepareStmt

func WithPrepareStmt(enabled bool) Option

WithPrepareStmt 设置 GORM 是否缓存预编译语句以提升后续执行性能。

func WithPrepareStmtCache

func WithPrepareStmtCache(maxSize int, ttl time.Duration) Option

WithPrepareStmtCache 设置预编译语句缓存的最大条数 maxSize 与存活时间 ttl。 仅在 WithPrepareStmt(true) 时生效;未设置时沿用 GORM 的缓存默认。

func WithQueryFields

func WithQueryFields(enabled bool) Option

WithQueryFields 设置查询时是否按模型字段名逐列展开 SELECT,而非 SELECT *。

func WithReadTimeout

func WithReadTimeout(timeout time.Duration) Option

WithReadTimeout 设置 I/O 读超时时间。默认 30s。

func WithServerVersion

func WithServerVersion(version string) Option

WithServerVersion 手动指定 MySQL 服务端版本号,供方言据此调整行为。 仅在 WithSkipInitializeWithVersion(true) 时生效——否则会被 GORM 的 SELECT VERSION() 结果覆盖; 且跳过版本探测后,GORM 不再据版本自动推导兼容标志,需要时由调用方自行处理。

func WithSingularTable

func WithSingularTable(enabled bool) Option

WithSingularTable 设置是否使用单数表名(如 User 对应表 user 而非 users)。

func WithSkipDefaultTransaction

func WithSkipDefaultTransaction(skip bool) Option

WithSkipDefaultTransaction 设置是否跳过 GORM 对单条写操作的默认事务包装。

func WithSkipInitializeWithVersion

func WithSkipInitializeWithVersion(skip bool) Option

WithSkipInitializeWithVersion 设置是否跳过初始化时根据服务端版本自动配置方言。

func WithStartupPing

func WithStartupPing(enabled bool) Option

WithStartupPing 设置打开连接时是否先执行 Ping 验证连通性。默认开启。

func WithStartupPingRetry

func WithStartupPingRetry(maxRetries int, baseWait, maxWait time.Duration) Option

WithStartupPingRetry 配置启动 Ping 的重试策略:maxRetries 为最大重试次数, baseWait、maxWait 为退避等待的基准值与上限。maxRetries 为负、baseWait 或 maxWait 非正时,对应项被忽略并保留原值。默认不重试,基准 1s,上限 5s。

func WithSystemVariable added in v1.2.0

func WithSystemVariable(key, value string) Option

WithSystemVariable 追加一个连接系统变量:连接建立后驱动会执行 `SET key = value`, 因此 value 必须是合法的 SQL 表达式(如字符串需自带引号)。它不是 DSN 内置参数—— loc、parseTime、timeout、charset 等 DSN 内置参数由专用 Option (WithLocation/WithParseTime/WithTimeout/WithCharset)处理,请勿经此设置。 SystemVariables 为 nil 时自动初始化,同名 key 会被覆盖;空 key 会在 Open 时返回错误。

安全边界:key/value 作为原始 SQL 直接拼接为 `SET` 语句执行,仅接受可信的静态配置; 切勿传入 HTTP 参数、用户配置等不可信输入,否则存在会话级 SQL 注入风险。

func WithSystemVariables added in v1.2.0

func WithSystemVariables(params map[string]string) Option

WithSystemVariables 批量追加连接系统变量(语义同 WithSystemVariable:连接后 `SET key = value`),同名 key 会被覆盖; 传入 nil 或空 map 时不做任何修改。

func WithTLSConfig

func WithTLSConfig(name string) Option

WithTLSConfig 设置 MySQL 驱动使用的 TLS 配置名称。支持驱动内置值 "true"、"false"、"skip-verify"、"preferred",也支持经 mysql.RegisterTLSConfig 注册的名称。生产环境推荐 "true" 或启用证书验证的自定义配置; "preferred" 在服务端不支持 TLS 时会回退为明文连接, "skip-verify" 加密但不验证服务端证书,两者仅适合受控环境。

func WithTablePrefix

func WithTablePrefix(prefix string) Option

WithTablePrefix 设置命名策略中的表名前缀,仅修改该字段,不影响策略的其他配置。

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout 设置建立连接(拨号)超时时间。默认 10s。

func WithTranslateError

func WithTranslateError(enabled bool) Option

WithTranslateError 设置是否将驱动错误翻译为 GORM 统一错误类型(如 gorm.ErrDuplicatedKey)。

func WithTxRetryObserver

func WithTxRetryObserver(observer TxRetryObserver) Option

WithTxRetryObserver 设置事务重试观察者,事务发生重试时回调通知重试事件。

func WithUser

func WithUser(user string) Option

WithUser 设置连接用户名。

func WithWriteTimeout

func WithWriteTimeout(timeout time.Duration) Option

WithWriteTimeout 设置 I/O 写超时时间。默认 30s。

func WithZapLogger added in v1.3.0

func WithZapLogger(zlog *zap.Logger, opts ...zlogger.Option) Option

WithZapLogger 用给定的 *zap.Logger 构造 GORM 日志器并注入,是接入 zap 的最短路径, 等价于 WithZlogger(zlogger.WithLogger(zlog), opts...)。zlog 为 nil 时回退为 no-op(静默丢弃)。 opts 在 logger 注入之后按序应用;默认级别 Warn、慢查询 200ms、参数化查询开启(不记录绑定参数值)。

Example

WithZapLogger 直传 *zap.Logger 一步接入 SQL 日志, 等价于 WithZlogger(zlogger.WithLogger(zlog), opts...)。

package main

import (
	"fmt"
	"time"

	"github.com/gtkit/ormx"
	"github.com/gtkit/ormx/zlogger"

	"go.uber.org/zap"
)

func main() {
	cfg := ormx.NewConfig(
		ormx.WithZapLogger(zap.NewNop(), zlogger.WithSlowThreshold(300*time.Millisecond)),
	)

	fmt.Println(cfg.GORM.Logger != nil)
}
Output:
true

func WithZlogger added in v1.1.3

func WithZlogger(opts ...zlogger.Option) Option

WithZlogger 用给定的 zlogger.Option 构造 GORM 日志器并注入, 等价于 WithGormLogger(zlogger.New(opts...)),省去调用方显式调用 zlogger.New。 不传任何 Option 时使用 zlogger 默认配置(no-op logger、慢查询 200ms、级别 Warn)。 需要注入自定义 gormlogger.Interface 实现时改用 WithGormLogger。

Example

WithZlogger 一步注入 GORM SQL 日志器,无需显式调用 zlogger.New, 等价于 WithGormLogger(zlogger.New(opts...))。

package main

import (
	"fmt"

	"github.com/gtkit/ormx"
	"github.com/gtkit/ormx/zlogger"

	"go.uber.org/zap"
	gormlogger "gorm.io/gorm/logger"
)

func main() {
	cfg := ormx.NewConfig(
		ormx.WithZlogger(
			zlogger.WithLogger(zap.NewNop()),
			zlogger.WithLogLevel(gormlogger.Info),
			zlogger.WithIgnoreRecordNotFoundError(true),
		),
	)

	fmt.Println(cfg.GORM.Logger != nil)
}
Output:
true

type PoolConfig

type PoolConfig struct {
	MaxOpenConns    *int           `json:"max_open_conns"     yaml:"max_open_conns"`
	MaxIdleConns    *int           `json:"max_idle_conns"     yaml:"max_idle_conns"`
	ConnMaxLifetime *time.Duration `json:"conn_max_lifetime"  yaml:"conn_max_lifetime"`
	ConnMaxIdleTime *time.Duration `json:"conn_max_idle_time" yaml:"conn_max_idle_time"`
}

PoolConfig 描述 *sql.DB 连接池参数。 字段均为指针:nil 表示不设置、保持 database/sql 的原有行为; 非 nil(含显式 0)表示应用该值到连接池。 可直接赋值、经 JSON/YAML 映射,或用 DefaultConfig 与对应 Option 设置,效果一致。

type TxOption

type TxOption func(*txOptions)

TxOption 配置事务重试行为。

func WithMaxRetries

func WithMaxRetries(n int) TxOption

WithMaxRetries 设置死锁后的最大重试次数。 设为 0 表示禁用重试。默认值:3。

func WithRetryBaseWait

func WithRetryBaseWait(d time.Duration) TxOption

WithRetryBaseWait 设置指数退避的基础等待时间。 默认值:5ms。

func WithRetryMaxWait

func WithRetryMaxWait(d time.Duration) TxOption

WithRetryMaxWait 设置单次重试退避的最大等待时间。 默认值:50ms。

type TxRetryEvent

type TxRetryEvent struct {
	ClientName string
	Attempt    int
	MaxRetries int
	Wait       time.Duration
	Err        error
}

TxRetryEvent 描述一次事务死锁重试事件。

type TxRetryObserver

type TxRetryObserver func(ctx context.Context, event TxRetryEvent)

TxRetryObserver 在每次事务重试等待前被调用,用于观测重试事件(如记录日志、上报指标)。

Directories

Path Synopsis
Package paginator 提供基于 GORM 的通用分页查询执行器。
Package paginator 提供基于 GORM 的通用分页查询执行器。
Package zlogger 提供基于 zap 的 GORM 日志适配器, 实现 gorm.io/gorm/logger 的 Interface,支持慢查询阈值、 日志级别、忽略 ErrRecordNotFound、参数化 SQL 以及 trace ID 关联等配置。
Package zlogger 提供基于 zap 的 GORM 日志适配器, 实现 gorm.io/gorm/logger 的 Interface,支持慢查询阈值、 日志级别、忽略 ErrRecordNotFound、参数化 SQL 以及 trace ID 关联等配置。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL