GORM的字段类型推导源码解析
字段类型的推导原理
摘要:本文详细解析了 GORM 中字段类型映射到数据库类型的完整推导过程。核心结论是:当 Go 结构体字段为int类型且未显式指定size标签时,GORM 会根据其Kind推导出Size=64,最终在 MySQL 中映射为bigint。推导的优先级为:1) 显式SIZE标签;2) 根据 Go 类型推导DataType;3) 显式TYPE标签(可能覆盖DataType);4) 若Size仍为 0,则根据 Go 类型推导Size。最终,DataType和Size共同决定数据库中的具体类型(如int、bigint)。
😀为什么我PeopleModel.Age,在gorm标签里写的type:int,为什么生成的表是bigint? 首先,gorm会根据数据类型DataType和长度Size判断这个字段在数据库表里应该用什么类型。1.没有指定size,gorm会根据DataType进行反推,源码如下,go里int对应的size=64,对应到mysql里就是bigintiffield.Size==0{switchreflect.Indirect(fieldValue).Kind(){casereflect.Int,reflect.Int64,reflect.Uint,reflect.Uint64,reflect.Float64:field.Size=64casereflect.Int8,reflect.Uint8:field.Size=8casereflect.Int16,reflect.Uint16:field.Size=16casereflect.Int32,reflect.Uint32,reflect.Float32:field.Size=32}}2.指定了Size,比如:Ageint`gorm:"size:32"`,而int32这个长度,在mysql里对应int.综上,Size的来源有两种:标签size显式指定、gorm根据反射推导。 ┌────────┬────────────────────────┬────────────────────────────────────────────┐ │ 优先级 │ 来源 │ 例子 │ ├────────┼────────────────────────┼────────────────────────────────────────────┤ │1│ SIZE 标签 │ gorm:"size:32"→ field.Size=32│ ├────────┼────────────────────────┼────────────────────────────────────────────┤ │2│ Go 类型的 reflect.Kind │int→64,int32→32,int16→16,int8→8│ └────────┴────────────────────────┴────────────────────────────────────────────┘3.field.Size 的确定 SIZE 标签存在? ├─ 是 → 用 SIZE 标签的值 └─ 否 → 根据 Go 类型推断:int/int64/uint/uint64/float64→64int8/uint8→8int16/uint16→16int32/uint32/float32→32field.DataType 的确定 TYPE 标签存在? ├─ 是 → 匹配 schema 常量(int/uint/bool/float/string/time/bytes)? │ ├─ 是 → 用该常量(后续走GORM 内部映射,会参考 Size) │ └─ 否 → 当自定义类型原样透传给数据库,相当于跳过了gorm └─ 否 → 根据 Go 类型推断(int→Int,string→String...)4.当datatype和size都没有,gorm先推导哪个?1.读 SIZE 标签 → field.Size2.推导 Go 类型 → field.DataType3.读 TYPE 标签覆盖 → field.DataType4.Size 还是0? → 根据 Go 类型推导 field.Size5.既然先datatype后size,那么size的推导是根据字段后的类型,还是datatype? 是根据字段后的类型,源码如图iffield.Size==0{switchreflect.Indirect(fieldValue).Kind(){// ← 看的是 Go 类型casereflect.Int,reflect.Int64,reflect.Uint,reflect.Uint64,reflect.Float64:field.Size=64casereflect.Int8,reflect.Uint8:field.Size=8casereflect.Int16,reflect.Uint16:field.Size=16casereflect.Int32,reflect.Uint32,reflect.Float32:field.Size=32}}//根据type标签得到DataTypeifval,ok:=field.TagSettings["TYPE"];ok{lowerVal:=DataType(strings.ToLower(val))switchlowerVal{caseBool,Int,Uint,Float,String,Time,Bytes:field.DataType=lowerVal// ← 匹配到常量,走这里default:field.DataType=DataType(val)// ← 没匹配到,走这里}}//field 第 233 行:如果没写type,会根据 Go 类型推导 DataTypeswitchreflect.Indirect(fieldValue).Kind(){casereflect.Int,reflect.Int8,reflect.Int16,reflect.Int32,reflect.Int64:field.DataType=Int...}//根据DataType选择对应的类型switchfield.DataType{caseschema.Bool:return"boolean"caseschema.Int,schema.Uint:returndialector.getSchemaIntAndUnitType(field)caseschema.Float:returndialector.getSchemaFloatType(field)caseschema.String:returndialector.getSchemaStringType(field)caseschema.Time:returndialector.getSchemaTimeType(field)caseschema.Bytes:returndialector.getSchemaBytesType(field)default:returndialector.getSchemaCustomType(field)//里面是sqltype := string(field.DataType)}//根据Size的大小选择对应的类型长度func(dialector Dialector)getSchemaIntAndUnitType(field*schema.Field)string{switch{casefield.Size<=8:returnconstraint("tinyint")casefield.Size<=16:returnconstraint("smallint")casefield.Size<=24:returnconstraint("mediumint")casefield.Size<=32:returnconstraint("int")default:returnconstraint("bigint")// ← 你的 Age 走到了这里}}