Golang 反射整体解决封装示例 TypeOf ValueOf

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语法与字段定义分散到了各个地方,看起来不美观。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值