这几天golang社区对泛型的讨论非常多的,一片热火朝天的景象。对我们广大gopher来说总归是好事。 泛型很有可能会颠覆我们之前的很多设计,带着这种疑问和冲动,我准备尝试用golang泛型实现几个orm的常见功能。 本文并没完全实现通用的orm,只是探讨其实现的一种方式提供各位读者做借鉴。创建Table 虽然golang有了泛型,但是目前在标准库sql底层还没有改造,目前还有很多地方需要用到reflect。funcCreateTable〔Tany〕(dbsql。DB){varaTt:reflect。TypeOf(a)tableName:strings。ToLower(t。Name())vardescstringfori:0;it。NumField();i{columnsName:strings。ToLower(t。Field(i)。Name)varcolumnTypestringswitcht。Field(i)。Type。Kind(){casereflect。Int:columnTypeintegercasereflect。String:columnTypetext}desccolumnsNamecolumnTypeifit。NumField()1{desc,}}sqlStmt:fmt。Sprintf(createtableifnotexistss(s);,tableName,desc),err:db。Exec(sqlStmt)iferr!nil{log。Printf(q:s,err,sqlStmt)return}} 调用方式typePersonstruct{IDintNamestringAgeint}typeStudentstruct{IDintNamestringNostring}vardbsql。DBinitdb。。。CreateTable〔Person〕(db)CreateTable〔Student〕(db) 这个部分跟传统的orm使用上没有太大区别,没办法不使用反射的情况下,泛型的方式可能变得有点繁琐。写入数据funcCreate〔Tany〕(dbsql。DB,aT){没有办法这边还是得使用反射t:reflect。TypeOf(a)tableName:strings。ToLower(t。Name())varcolumns〔〕stringvarspacehold〔〕stringfori:0;it。NumField();i{columnsappend(columns,strings。ToLower(t。Field(i)。Name))spaceholdappend(spacehold,?)}tx,err:db。Begin()iferr!nil{log。Fatal(err)}stmt,err:tx。Prepare(fmt。Sprintf(insertintos(s)values(s),tableName,strings。Join(columns,,),strings。Join(spacehold,,)))iferr!nil{log。Fatal(err)}deferstmt。Close()v:reflect。ValueOf(a)varvalues〔〕anyfori:0;it。NumField();i{ifv。FieldByName(t。Field(i)。Name)。CanInt(){valuesappend(values,v。FieldByName(t。Field(i)。Name)。Int())}else{valuesappend(values,v。FieldByName(t。Field(i)。Name)。String())}},errstmt。Exec(values。。。)iferr!nil{panic(err)}tx。Commit()} 调用方式varp1Person{ID:1,Name:wida,}Create〔Person〕(db,p1)vars1Student{ID:1,Name:wida,No:1111,}Create〔Person〕(db,p1)Create〔Student〕(db,s1) 和创建table类似,写入数据好像比没有之前的orm有优势。读取数据 读取数据是非常高频的操作,所以我们稍作封装。typeClientstruct{dbsql。DB}typeQuery〔Tany〕struct{clientClient}funcNewQuery〔Tany〕(cClient)Query〔T〕{returnQuery〔T〕{client:c,}}反射到structfuncToStruct〔Tany〕(rowssql。Rows,toT)error{v:reflect。ValueOf(to)ifv。Elem()。Type()。Kind()!reflect。Struct{returnerrors。New(Expectastruct)}scanDest:〔〕any{}columnNames,:rows。Columns()addrByColumnName:map〔string〕any{}fori:0;iv。Elem()。NumField();i{oneValue:v。Elem()。Field(i)columnName:strings。ToLower(v。Elem()。Type()。Field(i)。Name)addrByColumnName〔columnName〕oneValue。Addr()。Interface()}for,columnName:rangecolumnNames{scanDestappend(scanDest,addrByColumnName〔columnName〕)}returnrows。Scan(scanDest。。。)}func(qQuery〔T〕)FetchAll(ctxcontext。Context)(〔〕T,error){varitems〔〕TvaraTt:reflect。TypeOf(a)tableName:strings。ToLower(t。Name())rows,err:q。client。db。Query(SELECTFROMtableName)iferr!nil{returnnil,err}forrows。Next(){varcTToStruct(rows,c)itemsappend(items,c)}returnitems,nil} 调用方式varclientClient{db:db,}{query:NewQuery〔Person〕(client)all,err:query。FetchAll(context。Background())iferr!nil{log。Fatal(err)}for,person:rangeall{log。Println(person)}}{query:NewQuery〔Student〕(client)all,err:query。FetchAll(context。Background())iferr!nil{log。Fatal(err)}for,person:rangeall{log。Println(person)}} 稍微比原先的orm方式有了多一点想象空间,比如在〔Tany〕做更明确的约束,比如要求实现Filter定制方法。总结 鉴于本人能力还认证有限,目前还没有发现泛型对orm剧烈的改进和突破的可能。未来如果go对底层sql做出改动,或者实现诸如Rust那种Enum方式,可能会带来更多的惊喜。