XORM中存储JSON等非原生类型必须同时实现sql.Scanner和driver.Valuer接口,Scanner负责反序列化读取,Valuer负责序列化写入,缺一不可;未实现二者时字段被跳过。

在XORM中存储JSON、数组等非原生数据库类型时,必须让Go结构体字段实现sql.Scanner和driver.Valuer接口,否则XORM无法自动完成入库与查出的类型转换,直接报错或存空值。
为什么必须同时实现Scanner和Valuer
Scanner负责从数据库读取原始字节(如[]byte)并反序列化为Go结构体;Valuer负责把Go结构体序列化为数据库可存的格式(通常是[]byte或string)。缺一不可——只实现Scan会导致插入失败,只实现Value会导致查询返回零值。
这一步是硬性前提,【未同时实现两个接口的类型,XORM会跳过该字段,不写入也不读取】。
定义带JSON字段的结构体
先声明一个含JSON数据的结构体,例如用户偏好设置:
type UserPreference struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"index"`
Data []byte `xorm:"json"`
}
注意:这里暂用[]byte占位,实际要用自定义类型封装逻辑。XORM的xorm:"json"标签仅对内置json.RawMessage或实现了Scanner/Valuer的类型生效,不能直接套在普通切片上。
方法一:为结构体实现Scanner与Valuer
定义一个新类型包装JSON数据:
type Preference struct {
Likes []string `json:"likes"`
Theme string `json:"theme"`
Notify bool `json:"notify"`
}
然后为*Preference实现Scan,为Preference实现Value:
func (p *Preference) Scan(value interface{}) error {
if value == nil { return nil }
bytes, ok := value.([]byte)
if !ok { return fmt.Errorf("cannot scan %T into Preference", value) }
return json.Unmarshal(bytes, p)
}
func (p Preference) Value() (driver.Value, error) {
return json.Marshal(p)
}
关键点:Scan必须接收指针接收者(*Preference),否则无法修改原值;Value必须是值接收者(Preference),否则XORM调用时可能panic。
方法二:为基础类型实现(如uint8枚举)
适用于状态码、类型标识等小整数映射:
type Status uint8
const (
StatusActive Status = iota
StatusInactive
StatusPending
)
func (s *Status) Scan(value interface{}) error {
if value == nil { return nil }
switch v := value.(type) {
case int64: *s = Status(v)
case []byte: i, _ := strconv.ParseUint(string(v), 10, 8); *s = Status(i)
default: return fmt.Errorf("cannot scan %T into Status", value)
}
return nil
}
func (s Status) Value() (driver.Value, error) {
return int64(s), nil
}
这个实现兼容MySQL中TINYINT字段,避免了字符串存储带来的索引失效问题。
注册自定义类型到XORM引擎
完成接口实现后,必须在初始化XORM Engine后显式注册类型映射,否则XORM仍按默认规则处理:
engine.RegisterDialect(&mymysql.Dialect{}) // 或其他dialect
engine.RegisterModel(new(User))
接着调用:
engine.MapType(reflect.TypeOf(Preference{}), "text")
其中"text"表示该类型在MySQL中对应TEXT字段;若用PostgreSQL,可填"jsonb"。这一步告诉XORM:“遇到Preference类型,就用TEXT列来存”。【不调用MapType,XORM会尝试用VARCHAR(255)存,超长直接截断】。
使用自定义类型建表与写入
第一步:确保结构体字段使用自定义类型而非基础类型
type User struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"varchar(50)"`
Pref Preference `xorm:"json"`
}
第二步:执行Sync2自动建表
err := engine.Sync2(new(User))
第三步:插入数据
u := &User{
Name: "Alice",
Pref: Preference{
Likes: []string{"go", "xorm"},
Theme: "dark",
Notify: true,
}
}
_, err = engine.Insert(u)
此时XORM会调用Pref.Value()得到JSON字节流,写入TEXT字段;查询时自动触发Pref.Scan()还原结构体。


















