1. driver.Valuer
type Valuer interface {
Value() (Value, error)
}
-
作用:定义了一个类型如何转化为数据库可理解的值。
-
使用场景:当你在
db.Exec
或stmt.Query
里传参时,如果参数实现了driver.Valuer
接口,那么database/sql
会调用它的Value()
方法,把你的自定义类型转换成数据库驱动支持的基本类型。 -
常见实现:
-
time.Time
实现了Valuer
,这样才能存到数据库。 -
你也可以自定义一个类型(比如 JSON 字段),通过实现
Value()
把它转成[]byte
或string
。
-
例子:
type JSONValue map[string]interface{}
func (j JSONValue) Value() (driver.Value, error) {
b, err := json.Marshal(j)
if err != nil {
return nil, err
}
return string(b), nil
}
这样你在 db.Exec("INSERT ...", JSONValue{"a":1})
时会被正确存入。
2. driver.ValueConverter
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
-
作用:定义了数据库驱动如何把一个 通用的 Go 值 转换为驱动支持的
driver.Value
。 -
使用场景:这是 驱动实现者 用的,不是应用开发者日常直接使用的。 当
database/sql
把参数交给驱动时,会用ValueConverter
来做一次“归一化”。 -
常见实现:
-
driver.DefaultParameterConverter
就是标准库提供的默认实现。 -
它会把
int
,int64
,float64
,bool
,[]byte
,string
,time.Time
,nil
等转换成标准的driver.Value
类型。
-
例子(简化版逻辑):
func (DefaultParameterConverter) ConvertValue(v interface{}) (driver.Value, error) {
switch v := v.(type) {
case int:
return int64(v), nil
case string:
return v, nil
case []byte:
return v, nil
// ...
default:
return nil, fmt.Errorf("unsupported type %T", v)
}
}
3. 区别总结
-
Valuer:应用层开发者实现 → 把 自定义类型 转换成数据库能存的类型。 (比如 JSON、枚举、decimal 等)
-
ValueConverter:驱动层用来统一处理 → 把各种 Go 内置类型 归一化为
driver.Value
。 (一般你不会去实现,除非写驱动)
4. 类比
-
driver.Valuer
= 我要存的值怎么表达?(应用 → DB) -
driver.ValueConverter
= 驱动怎么理解这个值?(DB driver 内部处理)
5.实例分析
5.1数据库写入流程
以 db.Exec("INSERT INTO user(name) VALUES(?)", arg)
为例)
解释
先检查参数是否实现了 driver.Valuer
如果实现了,就调用 Value(),由开发者决定怎么序列化。 例如 time.Time.Value() 会返回 driver.Value 类型。
没有实现 Valuer 的话
交给 ValueConverter(通常是 driver.DefaultParameterConverter)做标准化转换。
例如把 int 转成 int64,把 float32 转成 float64,这样驱动统一处理。
最终交给驱动
驱动只接收规范化后的 driver.Value 类型,范围有限: int64, float64, bool, []byte, string, time.Time, nil
直观类比
driver.Valuer:你(开发者)告诉 DB:“我这个特殊类型该怎么转成数据库能存的类型”
driver.ValueConverter:DB 驱动说:“给我的 Go 基本类型,我帮你统一转换成我能吃的格式”
5.2数据库读取流程
(以 rows.Scan(&dest) 为例)
解释
数据库驱动返回值
驱动执行 SQL 后,返回的行数据一定是 driver.Value 类型, 范围有限:int64, float64, bool, []byte, string, time.Time, nil
Scan 过程
rows.Scan(&dest) 会依次把返回的列赋值给 dest。
优先检查 sql.Scanner 接口
如果 dest 实现了 sql.Scanner,那么调用它的 Scan(value interface{}) error 方法,交由你自己处理。
例如:
type JSONValue map[string]interface{}
func (j *JSONValue) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return fmt.Errorf("invalid type")
}
return json.Unmarshal(b, j)
}
否则走默认转换规则
Go 的 database/sql 会尝试把 driver.Value 转换成目标类型,比如:
string → string
[]byte → []byte 或 string
int64 → int, int64
time.Time → time.Time
如果类型不兼容则报错
比如数据库返回 string,但你用 Scan(&someStruct) 且它没实现 sql.Scanner,就会报错。
写入 vs 读取 对比
写入时
优先调用 driver.Valuer → 再走 driver.ValueConverter
读取时
优先调用 sql.Scanner → 再走默认类型转换
直观理解:
Valuer = “我要存的时候,怎么转成数据库能接受的值”
Scanner = “我要取的时候,怎么把数据库值还原成我的类型”
5.3完整示例
场景:我们有一个表 users
,里面有一个 profile
字段,类型是 JSON
。我们用自定义类型 JSONValue
来存取。
完整代码示例
package main
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
// 自定义类型,用于存 JSON
type JSONValue map[string]interface{}
// 实现 driver.Valuer —— 写入数据库时调用
func (j JSONValue) Value() (driver.Value, error) {
return json.Marshal(j) // 转换为 []byte 存到 JSON 字段
}
// 实现 sql.Scanner —— 读取数据库时调用
func (j *JSONValue) Scan(value interface{}) error {
if value == nil {
*j = nil
return nil
}
b, ok := value.([]byte)
if !ok {
return fmt.Errorf("JSONValue: invalid type %T", value)
}
return json.Unmarshal(b, j)
}
// user 表对应的结构体
type User struct {
ID int
Name string
Profile JSONValue // 用 JSONValue 存 profile
}
func main() {
// 请替换为你自己的 MySQL 配置
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True"
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 建表(如果不存在)
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
profile JSON
) ENGINE=InnoDB;
`)
if err != nil {
log.Fatal(err)
}
// 插入数据
profile := JSONValue{
"age": 20,
"hobby": []string{"coding", "gaming"},
}
_, err = db.Exec("INSERT INTO users (name, profile) VALUES (?, ?)", "Alice", profile)
if err != nil {
log.Fatal("Insert error:", err)
}
// 查询数据
rows, err := db.Query("SELECT id, name, profile FROM users")
if err != nil {
log.Fatal("Query error:", err)
}
defer rows.Close()
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Profile); err != nil {
log.Fatal("Scan error:", err)
}
fmt.Printf("User: %v\n", u)
}
}
运行效果
假设你运行后,表里有一条数据:
mysql> SELECT * FROM users;
+----+-------+-----------------------------------------+
| id | name | profile |
+----+-------+-----------------------------------------+
| 1 | Alice | {"age": 20, "hobby": ["coding","gaming"]} |
+----+-------+-----------------------------------------+
Go 程序输出:
User: {ID:1 Name:Alice Profile:map[age:20 hobby:[coding gaming]]}
关键点总结
-
写入时
Exec
的参数如果实现了driver.Valuer
,会调用Value()
→ 转换成driver.Value
(JSON 存入数据库)。 -
读取时
Scan
会先检查目标是否实现了sql.Scanner
。Profile JSONValue
实现了Scan(value)
,所以自动调用,解析 JSON → Go map。