Go database/sql/driver包里driver.ValueConverter和driver.Valuer的区别

1. driver.Valuer

type Valuer interface {
     Value() (Value, error)
}
  • 作用:定义了一个类型如何转化为数据库可理解的值。

  • 使用场景:当你在 db.Execstmt.Query 里传参时,如果参数实现了 driver.Valuer 接口,那么 database/sql 会调用它的 Value() 方法,把你的自定义类型转换成数据库驱动支持的基本类型。

  • 常见实现

    • time.Time 实现了 Valuer,这样才能存到数据库。

    • 你也可以自定义一个类型(比如 JSON 字段),通过实现 Value() 把它转成 []bytestring

例子:

 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]]}

关键点总结

  1. 写入时

    Exec 的参数如果实现了 driver.Valuer,会调用 Value() → 转换成 driver.Value(JSON 存入数据库)。
  2. 读取时

    Scan 会先检查目标是否实现了 sql.Scanner。                                                                  Profile JSONValue 实现了 Scan(value),所以自动调用,解析 JSON → Go map。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值