Skip to content

Latest commit

 

History

History
494 lines (401 loc) · 15.4 KB

File metadata and controls

494 lines (401 loc) · 15.4 KB

daog 架构设计文档

设计理念

核心原则

  1. 零反射开销: 通过编译时代码生成替代运行时反射,实现与原生 SQL 操作相同的性能
  2. 类型安全: 使用 Go 泛型提供编译时类型检查,避免运行时类型错误
  3. 简洁易用: 提供直观的 API,降低学习成本
  4. 安全可靠: 通过参数化查询防止 SQL 注入

设计灵感

daog 的设计思路来源于:

选择编译而非反射的原因是:基于编译的抽象没有性能损耗。

整体架构

┌─────────────────────────────────────────────────────────────────┐
│                        应用层 (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) │
                    └─────────────────┘

核心组件

1. Datasource (数据源)

数据源是数据库连接池的抽象,负责管理数据库连接的生命周期。

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 获取连接超时(秒)

2. TransContext (事务上下文)

事务上下文是所有数据库操作的执行环境,管理事务生命周期和连接资源。

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

3. TableMeta (表元数据)

表元数据描述数据库表与 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 时间戳列配置

4. Matcher (条件构建器)

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 组合

5. Modifier (更新构建器)

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 工具

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
}

日志系统

SQLLogger 接口

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)
}

日志配置

  1. 全局配置: 设置 daog.GLogger
  2. 数据源级别: 设置 DbConf.LogSQL
  3. 事务级别: 设置 TransContext.LogSQL

日志内容

  • SQL 语句和参数
  • 执行耗时
  • Goroutine ID
  • Trace ID
  • 错误信息

自定义类型

ttypes 包

类型 说明
NormalDate 日期类型(无时间)
NormalDatetime 日期时间类型
NilableDate 可空日期
NilableDatetime 可空日期时间
NilableString 可空字符串

所有类型都实现了:

  • driver.Valuer - 数据库写入
  • sql.Scanner - 数据库读取
  • json.Marshaler / json.Unmarshaler - JSON 序列化

最佳实践

1. 事务管理

// 推荐:使用 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())
}()

2. 错误处理

// 在事务函数中,使用同一个 err 变量
var err error
defer func() {
    tc.CompleteWithPanic(err, recover())
}()

result, err = doSomething()  // 使用 = 而非 :=
if err != nil {
    return err
}

3. 条件构建

// 推荐:使用 Matcher
matcher := daog.NewMatcher()
matcher.Eq("status", 1).Gt("age", 18)

// 避免:字符串拼接
// sql := "SELECT * FROM user WHERE status = " + status  // 危险!

4. 分页查询

pager := daog.NewPager(pageSize, pageNumber)
orders := []*daog.Order{daog.NewDescOrder("created_at")}
results, err := daog.QueryPageListMatcher(tc, matcher, meta, pager, orders...)