Skip to content

Latest commit

 

History

History
424 lines (338 loc) · 9.59 KB

File metadata and controls

424 lines (338 loc) · 9.59 KB

代码生成 (compilex)

daog 使用外部工具 compilex 将 CREATE TABLE 语句编译为 Go 代码。这种编译方式避免了运行时反射,实现了零性能损耗。

安装 compilex

go install github.com/rolandhe/compilex@latest

验证安装:

compilex -h

基本用法

compilex -i="schema.sql" -pkg dal -o ./dal

参数说明:

参数 说明 示例
-i 输入的 SQL 文件路径 -i="schema.sql"
-pkg 生成代码的包名 -pkg dal
-o 输出目录 -o ./dal

生成文件结构

每张表生成两个文件:

dal/
├── UserInfo.go        # 主文件(自动生成,不可修改)
└── UserInfo-ext.go    # 扩展文件(用户可修改)

主文件 (UserInfo.go)

主文件包含以下内容,由 compilex 自动生成,不应手动修改

package dal

import (
    "github.com/rolandhe/daog"
    "github.com/rolandhe/daog/ttypes"
)

// 1. 字段名常量
var UserInfoFields = struct {
    Id       string
    Name     string
    Email    string
    CreateAt string
    ModifyAt string
}{
    "id",
    "name",
    "email",
    "create_at",
    "modify_at",
}

// 2. 表元数据
var UserInfoMeta = &daog.TableMeta[UserInfo]{
    Table: "user_info",
    Columns: []string{
        "id",
        "name",
        "email",
        "create_at",
        "modify_at",
    },
    AutoColumn: "id",
    LookupFieldFunc: func(columnName string, ins *UserInfo, point bool) any {
        if "id" == columnName {
            if point {
                return &ins.Id
            }
            return ins.Id
        }
        if "name" == columnName {
            if point {
                return &ins.Name
            }
            return ins.Name
        }
        // ... 其他字段
        return nil
    },
    StampColumns: nil,
}

// 3. QuickDao 实例
var UserInfoDao daog.QuickDao[UserInfo] = &struct {
    daog.QuickDao[UserInfo]
}{
    daog.NewBaseQuickDao(UserInfoMeta),
}

// 4. 数据结构体
type UserInfo struct {
    Id       int64
    Name     string
    Email    string
    CreateAt ttypes.NormalDatetime
    ModifyAt ttypes.NilableDatetime
}

扩展文件 (UserInfo-ext.go)

扩展文件用于添加自定义逻辑,可以安全修改

package dal

func init() {
    // 在这里设置分表函数
    // UserInfoMeta.ShardingFunc = func(tableName string, key any) string {
    //     return fmt.Sprintf("%s_%d", tableName, key.(int64)%10)
    // }

    // 设置时间戳自动填充
    // UserInfoMeta.StampColumns = map[string]int{
    //     "create_at": 1,  // 插入时填充
    //     "modify_at": 3,  // 插入和更新都填充
    // }
}

// 可以添加自定义的数据访问方法
func GetUserByEmail(tc *daog.TransContext, email string) (*UserInfo, error) {
    matcher := daog.NewMatcher().Eq(UserInfoFields.Email, email)
    return UserInfoDao.QueryOneMatcher(tc, matcher)
}

类型映射规则

数值类型

MySQL 类型 Go 类型
TINYINT int8
SMALLINT int16
MEDIUMINT int32
INT / INTEGER int32
BIGINT int64
FLOAT float32
DOUBLE float64
DECIMAL decimal.Decimal

字符串类型

MySQL 类型 NOT NULL Go 类型
CHAR / VARCHAR / TEXT string
CHAR / VARCHAR / TEXT ttypes.NilableString

日期时间类型

MySQL 类型 NOT NULL Go 类型
DATE ttypes.NormalDate
DATE ttypes.NilableDate
DATETIME / TIMESTAMP ttypes.NormalDatetime
DATETIME / TIMESTAMP ttypes.NilableDatetime

二进制类型

MySQL 类型 Go 类型
BINARY / VARBINARY / BLOB []byte

LookupFieldFunc 原理

LookupFieldFunc 是 daog 实现零反射的核心。它根据列名返回结构体字段的值或指针:

LookupFieldFunc: func(columnName string, ins *UserInfo, point bool) any {
    if "id" == columnName {
        if point {
            return &ins.Id    // 返回指针,用于 Scan
        }
        return ins.Id         // 返回值,用于 Insert/Update
    }
    // ...
}

工作原理:

  1. 查询时: point=true,返回字段指针,用于 rows.Scan() 填充数据
  2. 写入时: point=false,返回字段值,用于构建 INSERT/UPDATE 参数

这种方式完全避免了 reflect 包的使用,性能与手写代码相同。

时间戳自动填充

通过配置 StampColumns 实现创建时间、修改时间的自动填充。

配置方式

在扩展文件的 init() 函数中配置:

func init() {
    UserInfoMeta.StampColumns = map[string]int{
        "create_at": 1,  // 仅插入时自动填充
        "modify_at": 2,  // 仅更新时自动填充
        "update_at": 3,  // 插入和更新都自动填充
    }
}

配置值说明:

说明
1 仅 INSERT 时自动填充(适合 create_at)
2 仅 UPDATE 时自动填充
3 INSERT 和 UPDATE 都自动填充(适合 modify_at)

自动填充行为

配置后,daog 会在写入操作时:

  1. 排除配置的时间戳列(不使用用户传入的值)
  2. 调用 ChangeFieldOfInsBeforeWrite 回调填充当前时间

需要设置全局回调:

import (
    "time"
    "github.com/rolandhe/daog"
    "github.com/rolandhe/daog/ttypes"
)

func init() {
    daog.ChangeFieldOfInsBeforeWrite = func(extInfo map[string]any, extractor daog.FieldExtractor) error {
        // 从字段名获取指针并填充当前时间
        if createAt := extractor.Extract("create_at"); createAt != nil {
            if ptr, ok := createAt.(*ttypes.NormalDatetime); ok {
                *ptr = ttypes.NormalDatetime(time.Now())
            }
        }
        if modifyAt := extractor.Extract("modify_at"); modifyAt != nil {
            if ptr, ok := modifyAt.(*ttypes.NormalDatetime); ok {
                *ptr = ttypes.NormalDatetime(time.Now())
            }
        }
        return nil
    }
}

自增 ID 回填

对于自增主键,daog 会在插入后自动回填生成的 ID:

user := &dal.UserInfo{Name: "张三"}
_, err := dal.UserInfoDao.Insert(tc, user)

// user.Id 已经被回填为数据库生成的自增 ID
fmt.Println(user.Id)  // 例如: 12345

实现原理:

  1. TableMeta.AutoColumn 记录自增列名
  2. 插入时排除自增列
  3. 插入后通过 result.LastInsertId() 获取生成的 ID
  4. 使用 LookupFieldFunc 获取字段指针并回填

多表生成

如果 SQL 文件包含多个 CREATE TABLE 语句,会为每张表生成对应的文件:

# schema.sql 包含 user_info 和 order_info 两张表
compilex -i="schema.sql" -pkg dal -o ./dal

生成结果:

dal/
├── UserInfo.go
├── UserInfo-ext.go
├── OrderInfo.go
└── OrderInfo-ext.go

重新生成

当表结构变更时,需要重新运行 compilex:

compilex -i="schema.sql" -pkg dal -o ./dal

注意事项:

  • 主文件会被覆盖
  • 扩展文件(-ext.go)不会被覆盖,保留用户的自定义代码
  • 如果新增了列,需要更新扩展文件中的相关逻辑

命名规则

表名 → 结构体名

  • 下划线分隔转驼峰
  • 首字母大写
表名 结构体名
user_info UserInfo
order_detail OrderDetail
sys_config SysConfig

列名 → 字段名

  • 下划线分隔转驼峰
  • 首字母大写
列名 字段名
id Id
user_name UserName
created_at CreatedAt

完整示例

输入 SQL

CREATE TABLE `group_info` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(128) NOT NULL COMMENT '群名称',
  `main_data` varchar(512) NOT NULL DEFAULT '' COMMENT '主要数据',
  `content` text NOT NULL COMMENT '内容',
  `bin_data` blob COMMENT '二进制数据',
  `create_at` datetime NOT NULL COMMENT '创建时间',
  `total_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '总金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群组信息表';

生成命令

compilex -i="group_info.sql" -pkg dal -o ./dal

生成的主文件

package dal

import (
    "github.com/rolandhe/daog"
    "github.com/rolandhe/daog/ttypes"
    "github.com/shopspring/decimal"
)

var GroupInfoFields = struct {
    Id          string
    Name        string
    MainData    string
    Content     string
    BinData     string
    CreateAt    string
    TotalAmount string
}{
    "id",
    "name",
    "main_data",
    "content",
    "bin_data",
    "create_at",
    "total_amount",
}

var GroupInfoMeta = &daog.TableMeta[GroupInfo]{
    Table: "group_info",
    Columns: []string{
        "id",
        "name",
        "main_data",
        "content",
        "bin_data",
        "create_at",
        "total_amount",
    },
    AutoColumn: "id",
    LookupFieldFunc: func(columnName string, ins *GroupInfo, point bool) any {
        // ... 字段映射逻辑
    },
    StampColumns: nil,
}

var GroupInfoDao daog.QuickDao[GroupInfo] = &struct {
    daog.QuickDao[GroupInfo]
}{
    daog.NewBaseQuickDao(GroupInfoMeta),
}

type GroupInfo struct {
    Id          int64
    Name        string
    MainData    string
    Content     string
    BinData     []byte
    CreateAt    ttypes.NormalDatetime
    TotalAmount decimal.Decimal
}

相关文档