本文章不做全面比较,只是比较当数据表存在null字段时,基于gorm库和sql标准库的一个重要的差异。
假设mysql存在如下一张表:
CREATE TABLE `student` (
`id` int NOT NULL,
`name` varchar(20) DEFAULT '',
`score` int DEFAULT NULL,
`classes` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3
复制代码
mysql> select * from student;
+----+-------+-------+---------+
| id | name | score | classes |
+----+-------+-------+---------+
| 1 | test1 | 100 | 3 |
| 2 | test2 | 100 | NULL |
+----+-------+-------+---------+
2 rows in set (0.00 sec)
复制代码
ps: 当然一般创建数据表时,规范的做法是,字段需要not null default 0指定默认值。这里只是为了演示两种库的差异。
- gorm
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Student struct {
Name string
Id int
Score float64
Classes int
AvgScore float64
}
func main() {
dsn := "root:12345678@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
var stu Student
db.Select("id,name,score,classes,score/classes as avg_score").Table("student").Where("id=?", 2).Find(&stu)
fmt.Println(stu)
}
复制代码
输出:
{test2 2 100 0 0}
复制代码
可以看出null值被转换成了0,而且没有异常抛出。
- sql
package main
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type Student struct {
Name string
Id int
Score float64
Classes int
AvgScore float64
}
func main() {
db, err := sql.Open("mysql", "root:12345678@tcp(127.0.0.1:3306)/test")
if err != nil {
panic(err)
}
rows, err := db.Query("select id,name,score,classes,score/classes as avg_score from student")
if err != nil {
panic(err)
}
var stu Student
for rows.Next() {
err := rows.Scan(&stu.Id, &stu.Name, &stu.Score, &stu.Classes, &stu.AvgScore)
if err != nil {
panic(err)
}
fmt.Println(stu)
}
}
复制代码
输出:
{test1 1 100 3 33.3333}
panic: sql: Scan error on column index 3, name "classes": converting NULL to int is unsupported
goroutine 1 [running]:
main.main()
/xxx/core/sqls/yyy/test2.go:34 +0x2d9
exit status 2
复制代码
类型转换的时候抛了异常。查看Scan代码,包含一个convertAssignRows调用:
switch dv.Kind() {
case reflect.Ptr:
if src == nil {
dv.Set(reflect.Zero(dv.Type()))
return nil
}
dv.Set(reflect.New(dv.Type().Elem()))
return convertAssignRows(dv.Interface(), src, rows)
//目标类型为如下
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
//结果类型为nil
if src == nil {
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
}
s := asString(src)
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetInt(i64)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if src == nil {
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
}
s := asString(src)
u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetUint(u64)
return nil
case reflect.Float32, reflect.Float64:
if src == nil {
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
}
s := asString(src)
f64, err := strconv.ParseFloat(s, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetFloat(f64)
return nil
case reflect.String:
if src == nil {
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
}
switch v := src.(type) {
case string:
dv.SetString(v)
return nil
case []byte:
dv.SetString(string(v))
return nil
}
}
复制代码
当查询结果类型为nil, 目标类型为int时,会抛类型转换的异常。但是如果目标类型为指针类型,不会抛异常,所以将代码改为:
package main
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type Student struct {
Name string
Id int
Score float64
Classes *int
AvgScore *float64
}
func main() {
db, err := sql.Open("mysql", "root:12345678@tcp(127.0.0.1:3306)/test")
if err != nil {
panic(err)
}
rows, err := db.Query("select id,name,score,classes,score/classes as avg_score from student")
if err != nil {
panic(err)
}
var stu Student
for rows.Next() {
err := rows.Scan(&stu.Id, &stu.Name, &stu.Score, &stu.Classes, &stu.AvgScore)
if err != nil {
panic(err)
}
fmt.Println(stu)
}
}
复制代码
输出:
{test1 1 100 0xc000016358 0xc000016360}
{test2 2 100 <nil> <nil>}
复制代码
结论:基于SQL库的查询需要注意结果字段为NULL的情况,对应结构体字段需要声明为指针类型。
那么,为什么基于gorm库的不需要考虑这种情况呢?
// assign stmt.ReflectValue
if stmt.Dest != nil {
stmt.ReflectValue = reflect.ValueOf(stmt.Dest)
for stmt.ReflectValue.Kind() == reflect.Ptr {
if stmt.ReflectValue.IsNil() && stmt.ReflectValue.CanAddr() {
stmt.ReflectValue.Set(reflect.New(stmt.ReflectValue.Type().Elem()))
}
stmt.ReflectValue = stmt.ReflectValue.Elem()
}
if !stmt.ReflectValue.IsValid() {
db.AddError(ErrInvalidValue)
}
}
复制代码
因为gorm库本身已经做了这种兼容处理。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END