daog 使用外部工具 compilex 将 CREATE TABLE 语句编译为 Go 代码。这种编译方式避免了运行时反射,实现了零性能损耗。
go install github.com/rolandhe/compilex@latest验证安装:
compilex -hcompilex -i="schema.sql" -pkg dal -o ./dal参数说明:
| 参数 | 说明 | 示例 |
|---|---|---|
-i |
输入的 SQL 文件路径 | -i="schema.sql" |
-pkg |
生成代码的包名 | -pkg dal |
-o |
输出目录 | -o ./dal |
每张表生成两个文件:
dal/
├── UserInfo.go # 主文件(自动生成,不可修改)
└── UserInfo-ext.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
}扩展文件用于添加自定义逻辑,可以安全修改:
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 是 daog 实现零反射的核心。它根据列名返回结构体字段的值或指针:
LookupFieldFunc: func(columnName string, ins *UserInfo, point bool) any {
if "id" == columnName {
if point {
return &ins.Id // 返回指针,用于 Scan
}
return ins.Id // 返回值,用于 Insert/Update
}
// ...
}工作原理:
- 查询时:
point=true,返回字段指针,用于rows.Scan()填充数据 - 写入时:
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 会在写入操作时:
- 排除配置的时间戳列(不使用用户传入的值)
- 调用
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
}
}对于自增主键,daog 会在插入后自动回填生成的 ID:
user := &dal.UserInfo{Name: "张三"}
_, err := dal.UserInfoDao.Insert(tc, user)
// user.Id 已经被回填为数据库生成的自增 ID
fmt.Println(user.Id) // 例如: 12345实现原理:
TableMeta.AutoColumn记录自增列名- 插入时排除自增列
- 插入后通过
result.LastInsertId()获取生成的 ID - 使用
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 |
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 ./dalpackage 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
}