本文章不做全面比较,只是比较当数据表存在null字段时,基于gorm库和sql标准库的一个重要的差异。假设mysql存在如下一张表:
CREATETABLE`student`(`id`intNOTNULL,`name`varchar(20)DEFAULT'',`score`intDEFAULTNULL,`classes`intDEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb3
mysql>select*fromstudent;+----+-------+-------+---------+|id|name|score|classes|+----+-------+-------+---------+|1|test1|100|3||2|test2|100|NULL|+----+-------+-------+---------+2rowsinset(0.00sec)
ps: 当然一般创建数据表时,规范的做法是,字段需要not null default 0指定默认值。这里只是为了演示两种库的差异。
gorm
packagemainimport("fmt""gorm.io/driver/mysql""gorm.io/gorm")typeStudentstruct{NamestringIdintScorefloat64ClassesintAvgScorefloat64}funcmain(){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{})iferr!=nil{panic(err)}varstuStudentdb.Select("id,name,score,classes,score/classesasavg_score").Table("student").Where("id=?",2).Find(&stu)fmt.Println(stu)}
输出:
{test2210000}
可以看出null值被转换成了0,而且没有异常抛出。
sql
packagemainimport("fmt""database/sql"_"github.com/go-sql-driver/mysql")typeStudentstruct{NamestringIdintScorefloat64ClassesintAvgScorefloat64}funcmain(){db,err:=sql.Open("mysql","root:12345678@tcp(127.0.0.1:3306)/test")iferr!=nil{panic(err)}rows,err:=db.Query("selectid,name,score,classes,score/classesasavg_scorefromstudent")iferr!=nil{panic(err)}varstuStudentforrows.Next(){err:=rows.Scan(&stu.Id,&stu.Name,&stu.Score,&stu.Classes,&stu.AvgScore)iferr!=nil{panic(err)}fmt.Println(stu)}}
输出:
{test11100333.3333}panic:sql:Scanerroroncolumnindex3,name"classes":convertingNULLtointisunsupportedgoroutine1[running]:main.main()/xxx/core/sqls/yyy/test2.go:34+0x2d9exitstatus2
类型转换的时候抛了异常。查看Scan代码,包含一个convertAssignRows调用:
switchdv.Kind(){casereflect.Ptr:ifsrc==nil{dv.Set(reflect.Zero(dv.Type()))returnnil}dv.Set(reflect.New(dv.Type().Elem()))returnconvertAssignRows(dv.Interface(),src,rows)//目标类型为如下casereflect.Int,reflect.Int8,reflect.Int16,reflect.Int32,reflect.Int64://结果类型为nilifsrc==nil{returnfmt.Errorf("convertingNULLto%sisunsupported",dv.Kind())}s:=asString(src)i64,err:=strconv.ParseInt(s,10,dv.Type().Bits())iferr!=nil{err=strconvErr(err)returnfmt.Errorf("convertingdriver.Valuetype%T(%q)toa%s:%v",src,s,dv.Kind(),err)}dv.SetInt(i64)returnnilcasereflect.Uint,reflect.Uint8,reflect.Uint16,reflect.Uint32,reflect.Uint64:ifsrc==nil{returnfmt.Errorf("convertingNULLto%sisunsupported",dv.Kind())}s:=asString(src)u64,err:=strconv.ParseUint(s,10,dv.Type().Bits())iferr!=nil{err=strconvErr(err)returnfmt.Errorf("convertingdriver.Valuetype%T(%q)toa%s:%v",src,s,dv.Kind(),err)}dv.SetUint(u64)returnnilcasereflect.Float32,reflect.Float64:ifsrc==nil{returnfmt.Errorf("convertingNULLto%sisunsupported",dv.Kind())}s:=asString(src)f64,err:=strconv.ParseFloat(s,dv.Type().Bits())iferr!=nil{err=strconvErr(err)returnfmt.Errorf("convertingdriver.Valuetype%T(%q)toa%s:%v",src,s,dv.Kind(),err)}dv.SetFloat(f64)returnnilcasereflect.String:ifsrc==nil{returnfmt.Errorf("convertingNULLto%sisunsupported",dv.Kind())}switchv:=src.(type){casestring:dv.SetString(v)returnnilcase[]byte:dv.SetString(string(v))returnnil}}
当查询结果类型为nil, 目标类型为int时,会抛类型转换的异常。但是如果目标类型为指针类型,不会抛异常,所以将代码改为:
packagemainimport("fmt""database/sql"_"github.com/go-sql-driver/mysql")typeStudentstruct{NamestringIdintScorefloat64Classes*intAvgScore*float64}funcmain(){db,err:=sql.Open("mysql","root:12345678@tcp(127.0.0.1:3306)/test")iferr!=nil{panic(err)}rows,err:=db.Query("selectid,name,score,classes,score/classesasavg_scorefromstudent")iferr!=nil{panic(err)}varstuStudentforrows.Next(){err:=rows.Scan(&stu.Id,&stu.Name,&stu.Score,&stu.Classes,&stu.AvgScore)iferr!=nil{panic(err)}fmt.Println(stu)}}
输出:
{test111000xc0000163580xc000016360}{test22100<nil><nil>}
结论:基于SQL库的查询需要注意结果字段为NULL的情况,对应结构体字段需要声明为指针类型。那么,为什么基于gorm库的不需要考虑这种情况呢?
//assignstmt.ReflectValueifstmt.Dest!=nil{stmt.ReflectValue=reflect.ValueOf(stmt.Dest)forstmt.ReflectValue.Kind()==reflect.Ptr{ifstmt.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库本身已经做了这种兼容处理。