gorm:"embedded"是GORM扁平化嵌入的唯一有效标签,必须显式标注在导出的嵌入字段上,否则默认存为JSON列;冲突字段需用embeddedPrefix加前缀,且嵌入结构体中的time.Time字段需手动加autoCreateTime等标签才触发自动时间管理。

gorm:"embedded" 是扁平化嵌入的唯一有效标签
不加 gorm:"embedded",GORM 默认把嵌入结构体当普通字段处理,生成的数据库表里会多出一个 JSON 字符串列(比如 PostgreSQL 的 jsonb 类型),而不是把子结构字段展开成独立列。只有显式标注该标签,GORM 才会将嵌入结构体的字段“提升”到父表中。
常见错误现象:User 结构体嵌入了 Profile,但迁移后表里只有 profile 一列(类型为 jsonb 或 text),profile.age 和 profile.addr 并未作为单独字段存在。
-
gorm:"embedded"必须写在嵌入字段声明行,不能写在嵌入结构体定义上 - 嵌入字段必须是**导出字段**(首字母大写),否则 GORM 完全忽略它
- 若多个嵌入结构体含同名字段(如两个都含
ID),GORM 迁移时直接 panic 报错duplicated field name
嵌入字段冲突时用 embeddedPrefix 控制列名前缀
当多个嵌入结构体有重名字段(例如 CreatedAt、UpdatedAt),或你想避免命名污染,embeddedPrefix 就是必需的。它会在所有被嵌入字段名前自动加指定前缀,生成如 profile_age、profile_addr 这样的列名。
使用场景:用户信息分拆为 Profile 和 Contact 两个嵌入结构体,两者都有 UpdatedAt 字段,不加前缀就会冲突。
- 写法是
Profile Profile `gorm:"embedded;embeddedPrefix:profile_"` - 前缀末尾建议带下划线,避免与字段名连写歧义(比如
profileupdatedat) -
embeddedPrefix不影响 Go 层访问方式,仍用user.Profile.Age,只是映射到数据库时加前缀
匿名嵌入和具名嵌入在 GORM 中行为一致
很多人以为匿名嵌入(Square)能自动扁平化,而具名嵌入(Profile Profile)不行——这是误解。GORM 对二者一视同仁:是否扁平化,只取决于有没有 gorm:"embedded" 标签,跟是否匿名无关。
容易踩的坑:写了 Profile Profile 却漏掉标签,结果字段没展开;或者写了 Square(匿名)但加了 gorm:"-" 或其他干扰标签,导致 GORM 跳过该字段。
- 匿名嵌入字段名即结构体名(如
Square→ 字段名square),但 GORM 不会自动用它作列名前缀 - 具名嵌入字段名可自定义(如
PublicInfo Profile),配合embeddedPrefix:public_更清晰 - 无论匿名或具名,嵌入结构体内部字段仍需导出,且不能含
gorm:"-"等屏蔽标签
嵌入结构体里的 time.Time 字段会触发 GORM 自动时间追踪
如果嵌入结构体里定义了 CreatedAt、UpdatedAt、DeletedAt,且类型是 time.Time,GORM 会识别并自动管理这些字段——即使它们不在主结构体中定义。
这常被误认为“不生效”,其实是因为:GORM 只对主结构体的字段做默认时间填充,嵌入结构体里的同名字段默认不参与自动赋值,除非你显式启用:
- 在嵌入字段上加
gorm:"autoCreateTime;autoUpdateTime"才会触发自动写入 - 若嵌入结构体用了
embeddedPrefix,GORM 不会自动识别其CreatedAt,必须手动加标签 - 多个嵌入结构体都带
UpdatedAt,又没加前缀,迁移会失败;加了前缀后,每个都能独立受控
embedded 和可能需要的 embeddedPrefix。漏掉任何一个,就退回 JSON 字符串存储。


















