Golang的反射由于加入了指针以及*,&关键字的使用,使得其api很不好理解,尤其是TypeOf、ValueOf的分类,误导了很多人。
经过大量的试验和搜索查找,终于找到相对正确的解决方案
示例代码如下,要注意两点:
1、逐层寻址拿到interface类型才算结束。
2、TypeOf只能拿到字段定义信息,不能拿到实际的值(本人没有找到api)。但是ValueOf却是都可以拿到。
上ValueOf路线代码,最终返回tag中json名称与实际值的map对象,方便实现插入sql的生成:
func tagMatchV(bo interface{}) map[string]interface{} {
val := reflect.ValueOf(bo)
for val.Kind() != reflect.Struct {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() == reflect.Interface {
val = val.Elem()
}
}
match := make(map[string]interface{}, val.NumField())
for k := 0; k < val.NumField(); k++ {
tag := val.Type().Field(k).Tag.Get("json")//tag信息获取,比java优势的地方
if len(tag) > 0 && val.Field(k).CanInterface() && !val.Field(k).IsZero() {
switch val.Field(k).Kind() {//没有泛型,糟点
case reflect.String:
match[tag] = val.Field(k).String()
break
case reflect.Bool:
match[tag] = val.Field(k).Bool()
break
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
match[tag] = val.Field(k).Int()
break
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
match[tag] = val.Field(k).Uint()
break
case reflect.Float32, reflect.Float64:
match[tag] = val.Field(k).Float()
break
default:
match[tag] = val.Field(k).Interface()
break
}
}
}
return match
}
上TypeO路线,只能获取field信息:
func typeOf(entity interface{}) []string {
ptr := reflect.TypeOf(entity)
for ptr.Kind() != reflect.Struct {
if ptr.Kind() == reflect.Ptr {
ptr = ptr.Elem()
}
if ptr.Kind() == reflect.Interface {
ptr = reflect.TypeOf(ptr)
}
}
tableFiled := make([]string, ptr.NumField())
for k := 0; k < ptr.NumField(); k++ {
fieldName := ptr.Field(k).Tag.Get("json")
if len(fieldName) > 0 {
index := ptr.Field(k).Tag.Get("index")
if fieldName == "primary_key" {
fieldName = "id"
}
if len(index) > 0 {
i, err := strconv.ParseInt(index, 10, 64)
if err == nil && i >= 0 && i < int64(ptr.NumField()) {
tableFiled[i] = fieldName
} else {
tableFiled[k] = fieldName
}
} else {
tableFiled[k] = fieldName
}
}
}
return tableFiled
}
有了上面两种方案,写一个类似mybatis或者springtemplate的orm框架已经不成问题了(当然,动态代理没办法解决)。
我最终的方案是通过反射,获取定义的entity终tag对应的字段名称,然后获取sql中需要的字段名称数组,解决查询内容的问题。
然后根据map或者entity的定义,自动获取字段名称和值的map对象(调用tagMatchV方法),遍历map自动生成update或者where语句,完成整个sql语法的封装。
当然,也可以自定义where方法传入,到时候回调即可。
上自动生成update语句示例:
func UpdateSql(bo interface{}, WhereSlice func(where []interface{}) string, where ...interface{}) (ssql string, params []interface{}) {
var wheresql = "1=1"
if WhereSlice != nil {
wheresql = WhereSlice(where)
}
match := tagMatchV(bo)
set := make([]string, 0, len(match))
params = make([]interface{}, 0, len(match))
for k, v := range match {
set = append(set, fmt.Sprintf("%s = ?", k))
params = append(params, v)
}
params = append(params, where...)
return fmt.Sprintf(UpdateSqlTemplate, "tableName", strings.Join(set, ","), wheresql), params
}
Golang中api比较麻烦的还有事务控制,习惯了spring的无感知事务处理,用起来实在反感(sql.Db和sql.Tx的差异),动态代理没有解决方案,只能靠匿名函数和defer来解决了。
上示例代码:
func (txProxy *TxProxy) Proxy(handler func() (interface{}, error)) (result interface{}, err error) {
if handler == nil {
return nil, nil
}
tx, err := ThisServer.Mysql.Begin()
if err != nil {
tlog.Errorf("db open tx error %s", err)
return nil, err
}
defer func() {
//if r := recover(); r != nil {
// tlog.Errorf("tx proxy error %s", r)
// tx.Rollback()
//} else {
if err != nil {
tlog.Errorf("tx proxy error %s", err.Error())
tx.Rollback()
} else {
tx.Commit()
}
//}
}()
txProxy.tx = tx
temp, err := handler()
if err != nil {
//tlog.Errorf("db open tx error %s", err)
return temp, err
}
return temp, nil
}
当然,要定义TxProxy结构体相对应的sql执行引擎,封装sql.Db和sql.Tx的差异。这样,写dao的时候就不需要做特殊处理了,把TxProxy实例作为参数传递就行了。
上示例代码:
//事务proxy使用示例
func dosomething(params ...interface{}){
txProxy := &TxProxy{}//先定义txproxy实例,并传入sql.db实例
_, err := txProxy.Proxy(func() (interface{}, error) {
err := UpdateOne(txProxy,params)
if err != nil{
return nil,err
}
err := UpdateTwo(txProxy,params)
if err != nil{
return nil,err
}
return nil,nil
})
}
//Dao 方法定义 示例
UpdateOne(txProxy *TxProxy,params ...interface{}){
txProxy.Exec(sql,params)
txProxy.Query(sql,params)
}
ok,整个orm及事务模版完工。当然,您也可以使用gorm,个人感觉gorm还不如不用,她还是把大量的sql语法与字段定义分散到了各个地方,看起来不美观。