golang mysql select * 优化

栏目: 数据库 · Mysql · 发布时间: 4年前

内容简介:初学者使用golang mysql做查询时,一般直接使用原生的select * from table来查询数据:后来数据表结构变动直接导致GetUserInfo()报错了,因为字段对应不上了。然后优化了一版:此时问题解决了,然后继续写类似的代码。

初学者使用golang mysql做查询时,一般直接使用原生的select * from table来查询数据:

type UserInfo struct {
    Id   int64
    Name string
    Sex  string
    //...
}

func GetUserInfo(uid int64) (info UserInfo, err error) {
    Db := lib.MysqlConn()
    sql := fmt.Sprintf("select * from user where uid=%d", uid)
    rows, err := Db.Query(sql)
    if err != nil {
        return info, err
    }
    defer Db.Close()

    if rows.Next() {
        var (
            id   int64
            name string
            sex  string
            //...
        )
        //得一个个的字段罗列出来
        err = rows.Scan(&id, &name, &sex, //...)
        info = UserInfo{
            Id:   id,
            Name: name,
            Sex:  sex,
            //...
        }
    }

    return info, nil
}

后来数据表结构变动直接导致GetUserInfo()报错了,因为字段对应不上了。然后优化了一版:

func GetUserInfo(uid int64) (info UserInfo, err error) {
    Db := lib.MysqlConn()
    sql := fmt.Sprintf("select `id`,`user_name`,`sex`,... from user where uid=%d", uid)
    rows, err := Db.Query(sql)
    if err != nil {
        return
    }
    defer Db.Close()

    if rows.Next() {
        //得一个个的字段罗列出来
        err = rows.Scan(&info.Id, &info.Name, &info.Sex, //...)
        if err != nil {
            return
        }
    }

    return
}

此时问题解决了,然后继续写类似的代码。

时间久了,就发现这样写好累,每次都得把所有的字段都罗列出来,而且上下一一对应。

所以就想能不能自动生成上下对应的变量,然后直接用变量来替换上。

如是,就用到了golang的反射,先定义一个结构体和数据表的字段一一对应,并在字段说明里面定义数据库的字段名:

type UserInfo struct {
	Id   int64  `sql:"id"`
	Name string `sql:"user_name"`
	Sex  string `sql:"sex"`
	//...
}

可以使用反射提取出类似`sql:"id"`里面id,然后拼装起来,具体如下

func (ui *UserInfo) allFields() (sqlFields string) {
	arr := []string{}
	el := reflect.TypeOf(ui).Elem()
	for i := 0; i < el.NumField(); i++ {
		arr = append(arr, el.Field(i).Tag.Get("sql"))
	}

	sqlFields = "`" + strings.Join(arr, "`,`") + "`"
	return
}

所以上面的函数中 sql 语句的定义就改为了:

//...
sql := fmt.Sprintf("select "+info.allFields()+" from user where uid=%d", uid)
//...

问题又来了,

rows.Scan(&info.Id, &info.Name, &info.Sex, //...)

rows.Scan怎么优化?

这里实际上就是每个字段的地址,如果取出该变量的地址,组成一个切片,然后展开切片就可以了,再来定义方法:

func (ui *UserInfo) allValues() (sqlValues []interface{}) {
	vl := reflect.ValueOf(ui).Elem()
	num := reflect.TypeOf(ui).Elem().NumField()
	for i := 0; i < num; i++ {
		sqlValues = append(sqlValues, vl.Field(i).Addr().Interface())
	}
	return
}

func GetUserInfo(uid int64) (info UserInfo, err error) {
	Db := lib.MysqlConn()
	sql := fmt.Sprintf("select "+info.allFields()+" from user where uid=%d", uid)
	rows, err := Db.Query(sql)
	if err != nil {
		return
	}
	defer Db.Close()

	if rows.Next() {
		//获取每个字段的切片
		fields := info.allValues()
		err = rows.Scan(fields...)
		if err != nil {
			return
		}
	}

	return
}

把 info.allFields()和info.allValues()合并在一起返回:

type UserInfo struct {
	Id   int64  `sql:"id"`
	Name string `sql:"user_name"`
	Sex  string `sql:"sex"`
	//...
}

func (ui *UserInfo) allFieldsAndValues() (sqlFields string, sqlValues []interface{}) {
	arr := []string{}
	el := reflect.TypeOf(ui).Elem()
	vl := reflect.ValueOf(ui).Elem()
	for i := 0; i < el.NumField(); i++ {
		arr = append(arr, el.Field(i).Tag.Get("sql"))
		sqlValues = append(sqlValues, vl.Field(i).Addr().Interface())
	}

	sqlFields = "`" + strings.Join(arr, "`,`") + "`"

	return
}

func GetUserInfo(uid int64) (info UserInfo, err error) {
	Db := lib.MysqlConn()

	sqlFields, sqlValues := info.allFieldsAndValues()
	sql := fmt.Sprintf("select "+sqlFields+" from user where uid=%d", uid)
	rows, err := Db.Query(sql)
	if err != nil {
		return
	}
	defer Db.Close()

	if rows.Next() {
		err = rows.Scan(sqlValues...)
	}

	return
}

现在封装起来后就方便多了,不担心数据表结构变动:这里只需要修改结构体UserInfo的信息。

现在又有一个方法需要批量的获取UserInfo,方法UserInfo.allFieldsAndValues()就大有用处了:

func GetAllUserInfo() (all []UserInfo, err error) {
	Db := lib.MysqlConn()
	info := UserInfo{}
	sqlFields, sqlValues := info.allFieldsAndValues()
	rows, err := Db.Query("select " + sqlFields + " from user ")
	if err != nil {
		return
	}
	defer Db.Close()

	for rows.Next() {
		err = rows.Scan(sqlValues...)
		if err != nil {
			return
		}
		all = append(all, info)
		info = UserInfo{}
	}

	return
}

这里在循环内[for rows.Next(){//...}]因为结构体的传值是值传递,all = append(all, info)操作的是info的一个拷贝。

用完之后在把变量info的值给重置掉:info = UserInfo{}。最后返回all。

Go语言中有的传参是值传递(传值),是一个副本,一个拷贝。

因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;

有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据

文章来源于 https://www.lixiaocheng.com/read/703


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编写高质量代码

编写高质量代码

秦小波 / 机械工业出版社华章公司 / 2011-12-28 / 59.00元

在通往“Java技术殿堂”的路上,本书将为你指点迷津!内容全部由Java编码的最佳实践组成,从语法、程序设计和架构、工具和框架、编码风格和编程思想等五大方面对Java程序员遇到的各种棘手的疑难问题给出了经验性的解决方案,为Java程序员如何编写高质量的Java代码提出了151条极为宝贵的建议。对于每一个问题,不仅以建议的方式从正反两面给出了被实践证明为十分优秀的解决方案和非常糟糕的解决方案,而且还......一起来看看 《编写高质量代码》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具