- 零反射开销: 通过编译时代码生成替代运行时反射,实现与原生 SQL 操作相同的性能
- 类型安全: 使用 Go 泛型提供编译时类型检查,避免运行时类型错误
- 简洁易用: 提供直观的 API,降低学习成本
- 安全可靠: 通过参数化查询防止 SQL 注入
daog 的设计思路来源于:
- Java 的 simpleGenericDao ORM 框架
- Protocol Buffers 的编译思路
选择编译而非反射的原因是:基于编译的抽象没有性能损耗。
┌─────────────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ QuickDao │ │ 函数 API │ │ Raw SQL │ │
│ │ (便捷接口) │ │ (泛型函数) │ │ (原生SQL) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
├─────────┴────────────────┴────────────────┴─────────────────────┤
│ 核心层 (Core) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ TableMeta │ │ Matcher │ │ Modifier │ │
│ │ (表元数据) │ │ (条件构建器) │ │ (更新构建器) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │TransContext │ │ View │ │ Pager/Order │ │
│ │ (事务上下文) │ │ (视图列) │ │ (分页/排序) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 数据源层 (Datasource) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ singleDatasource │ │ shardingDatasource │ │
│ │ (单库数据源) │ │ (分片数据源) │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 基础设施层 (Infrastructure) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ SQLLogger │ │ ttypes │ │ utils │ │
│ │ (日志) │ │ (自定义类型) │ │ (工具) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ MySQL Driver │
│ (go-sql-driver) │
└─────────────────┘
数据源是数据库连接池的抽象,负责管理数据库连接的生命周期。
type Datasource interface {
getDB(ctx context.Context) *sql.DB
Shutdown()
IsLogSQL() bool
acquireConnTimeout() int
}实现类型:
singleDatasource: 单库数据源,管理单个数据库的连接池shardingDatasource: 分片数据源,支持多库路由
配置参数 (DbConf):
| 参数 | 类型 | 说明 |
|---|---|---|
| DbUrl | string | MySQL 连接字符串 |
| Size | int | 最大连接数 |
| Life | int64 | 连接最大生命周期(秒) |
| IdleCons | int | 最大空闲连接数 |
| IdleTime | int64 | 最大空闲时间(秒) |
| LogSQL | bool | 是否记录 SQL |
| GetConnTimeout | int | 获取连接超时(秒) |
事务上下文是所有数据库操作的执行环境,管理事务生命周期和连接资源。
type TransContext struct {
txRequest txrequest.RequestStyle // 事务类型
tx driver.Tx // 数据库事务
conn *sql.Conn // 数据库连接
status tcStatus // 事务状态
ctx context.Context // 上下文(含元数据)
LogSQL bool // SQL 日志开关
ExtInfo map[string]any // 扩展信息
}事务类型:
| 类型 | 值 | 说明 |
|---|---|---|
| RequestNone | 0 | 无事务,每条 SQL 独立执行 |
| RequestReadonly | 1 | 只读事务,性能更优 |
| RequestWrite | 2 | 读写事务,支持所有操作 |
生命周期管理:
创建 ──► 使用 ──► 完成
│ │
│ ├── Complete(nil) ──► 提交
│ │
│ └── Complete(err) ──► 回滚
│
└── 必须通过 defer 确保调用 Complete
表元数据描述数据库表与 Go 结构体的映射关系,由 compilex 工具生成。
type TableMeta[T any] struct {
LookupFieldFunc func(columnName string, ins *T, point bool) any
ShardingFunc func(tableName string, shardingKey any) string
Table string
Columns []string
AutoColumn string
StampColumns map[string]int
}关键字段:
| 字段 | 说明 |
|---|---|
| LookupFieldFunc | 字段查找函数,避免反射 |
| ShardingFunc | 分表函数 |
| Table | 表名 |
| Columns | 列名列表 |
| AutoColumn | 自增列名 |
| StampColumns | 时间戳列配置 |
Matcher 用于安全构建 WHERE 子句,防止 SQL 注入。
type Matcher interface {
Eq(fieldName string, v any) Matcher
Ne(fieldName string, v any) Matcher
Lt(fieldName string, v any) Matcher
Lte(fieldName string, v any) Matcher
Gt(fieldName string, v any) Matcher
Gte(fieldName string, v any) Matcher
In(fieldName string, v []any) Matcher
NotIn(fieldName string, v []any) Matcher
Like(fieldName string, v any, likeStyle LikeStyle) Matcher
Null(fieldName string, not bool) Matcher
Between(fieldName string, start any, end any) Matcher
BitwiseAnd(fieldName string, v any, equalV any) Matcher
Add(m Matcher) Matcher
AddCond(cond SQLCond) Matcher
AddScalar(cond string) Matcher
ToSQL(args []any) (string, []any)
}条件组合:
NewMatcher() ──► AND 组合(默认)
NewAndMatcher() ──► AND 组合
NewOrMatcher() ──► OR 组合
Modifier 用于构建 UPDATE 语句的 SET 子句。
type Modifier interface {
Add(fieldName string, v any) Modifier
SelfAdd(fieldName string, v any) Modifier
SelfMinus(fieldName string, v any) Modifier
toSQL(tableName string) (string, []any, error)
getPureChangePairs() []*pair
}操作类型:
| 方法 | 生成 SQL |
|---|---|
| Add("status", 1) | status = ? |
| SelfAdd("count", 1) | count = count + ? |
| SelfMinus("stock", 1) | stock = stock - ? |
compilex 是独立的编译工具,将 CREATE TABLE 语句编译为 Go 代码。
./compilex -i="create_table.sql" -pkg dal -o ./dal每张表生成两个文件:
主文件 (TableName.go) - 不可修改:
// 字段名常量
var TableNameFields = struct {
Id string
Name string
}{
"id",
"name",
}
// 表元数据
var TableNameMeta = &daog.TableMeta[TableName]{
Table: "table_name",
Columns: []string{"id", "name"},
AutoColumn: "id",
LookupFieldFunc: func(columnName string, ins *TableName, point bool) any {
// 字段映射逻辑
},
}
// QuickDao 实例
var TableNameDao daog.QuickDao[TableName] = ...
// 数据结构体
type TableName struct {
Id int64
Name string
}扩展文件 (TableName-ext.go) - 可修改:
func init() {
// 设置分表函数
TableNameMeta.ShardingFunc = func(tableName string, key any) string {
return fmt.Sprintf("%s_%d", tableName, key.(int64)%10)
}
}
// 自定义方法
func CustomQuery(tc *daog.TransContext) ([]*TableName, error) {
// ...
}应用代码
│
▼
GetById(tc, id, meta)
│
├── 1. 检查 TransContext 状态
│
├── 2. 构建 SQL
│ └── SELECT columns FROM table WHERE id = ?
│
├── 3. 执行查询
│ └── tc.conn.QueryContext(ctx, sql, args...)
│
├── 4. 扫描结果
│ └── 使用 LookupFieldFunc 获取字段指针
│
└── 5. 返回结果
应用代码
│
▼
Insert(tc, ins, meta)
│
├── 1. 调用 BeforeInsertCallback(如果设置)
│
├── 2. 确定插入列(排除自增列、时间戳列)
│
├── 3. 自动填充时间戳字段
│
├── 4. 构建 SQL
│ └── INSERT INTO table(columns) VALUES(?)
│
├── 5. 执行插入
│ └── tc.conn.ExecContext(ctx, sql, args...)
│
├── 6. 获取自增 ID(如果有)
│ └── result.LastInsertId()
│
└── 7. 回填自增 ID 到结构体
通过 TableMeta.ShardingFunc 实现表分片:
// 在 -ext.go 文件中设置
func init() {
UserMeta.ShardingFunc = func(tableName string, key any) string {
userId := key.(int64)
return fmt.Sprintf("%s_%02d", tableName, userId%100)
}
}通过 DatasourceShardingPolicy 接口实现数据源分片:
type DatasourceShardingPolicy interface {
Shard(key any) (Datasource, error)
}内置实现:
ModInt64ShardingDatasourcePolicy: 基于 int64 取模的分片策略
使用 NewTransContextWithSharding 创建支持分片的事务上下文:
tc, err := daog.NewTransContextWithSharding(
datasource,
txrequest.RequestWrite,
"trace-id",
tableShardingKey, // 表分片键
datasourceShardingKey, // 数据源分片键
)| 回调 | 触发时机 | 用途 |
|---|---|---|
| BeforeInsertCallback | INSERT 前 | 数据校验、字段填充 |
| BeforeUpdateCallback | UPDATE 前 | 数据校验、字段填充 |
| BeforeModifyCallback | Modifier UPDATE 前 | 修改 Modifier |
| ChangeFieldOfInsBeforeWrite | 写入前 | 修改字段值 |
| AddNewModifyFieldBeforeUpdate | UPDATE 前 | 添加更新字段 |
// 设置插入前回调
daog.BeforeInsertCallback = func(tableName string, ins any) error {
// 自动填充创建时间
if v, ok := ins.(interface{ SetCreatedAt(time.Time) }); ok {
v.SetCreatedAt(time.Now())
}
return nil
}type SQLLogger interface {
Error(ctx context.Context, err error)
Info(ctx context.Context, content string)
ExecSQLBefore(ctx context.Context, sql string, argsJson string, sqlMd5 string)
ExecSQLAfter(ctx context.Context, sqlMd5 string, cost int64)
SimpleLogError(err error)
}- 全局配置: 设置
daog.GLogger - 数据源级别: 设置
DbConf.LogSQL - 事务级别: 设置
TransContext.LogSQL
- SQL 语句和参数
- 执行耗时
- Goroutine ID
- Trace ID
- 错误信息
| 类型 | 说明 |
|---|---|
| NormalDate | 日期类型(无时间) |
| NormalDatetime | 日期时间类型 |
| NilableDate | 可空日期 |
| NilableDatetime | 可空日期时间 |
| NilableString | 可空字符串 |
所有类型都实现了:
driver.Valuer- 数据库写入sql.Scanner- 数据库读取json.Marshaler/json.Unmarshaler- JSON 序列化
// 推荐:使用 AutoTrans
err := daog.AutoTrans(tcCreate, func(tc *daog.TransContext) error {
// 业务逻辑
return nil
})
// 手动管理时,必须使用 defer
tc, err := daog.NewTransContext(...)
if err != nil {
return err
}
defer func() {
tc.CompleteWithPanic(err, recover())
}()// 在事务函数中,使用同一个 err 变量
var err error
defer func() {
tc.CompleteWithPanic(err, recover())
}()
result, err = doSomething() // 使用 = 而非 :=
if err != nil {
return err
}// 推荐:使用 Matcher
matcher := daog.NewMatcher()
matcher.Eq("status", 1).Gt("age", 18)
// 避免:字符串拼接
// sql := "SELECT * FROM user WHERE status = " + status // 危险!pager := daog.NewPager(pageSize, pageNumber)
orders := []*daog.Order{daog.NewDescOrder("created_at")}
results, err := daog.QueryPageListMatcher(tc, matcher, meta, pager, orders...)