<_Go基础之--操作Mysql(⼆)
在上⼀篇⽂章中主要整理了Golang连接mysql以及⼀些基本的操作,并进⾏了⼤概介绍,这篇⽂章对增删查改进⾏详细的整理
读取数据
在上⼀篇⽂章中整理查询数据的时候,使⽤了Query的⽅法查询,其实database/sql还提供了QueryRow⽅法查询数据,就像之前说的database/sql连接创建都是惰性的,所以当我们通过Query查询数据的时候主要分为三个步骤:
从连接池中请求⼀个连接
执⾏查询的sql语句
将数据库连接的所属权传递给Result结果集
Query返回的结果集是sql.Rows类型。它有⼀个Next⽅法,可以迭代数据库的游标,进⽽获取每⼀⾏的数据,使⽤⽅法如下:
//执⾏查询操作
rows,err := Db.Query("SELECT email FROM user_info WHERE user_id>=5")if err !=nil{
fmt.Println("select db failed,err:",err)
return
}// 这⾥获取的rows是从数据库查的满⾜user_id>=5的所有⾏的email信息,rows.Next(),⽤于循环获取所有
for rows.Next(){
var s string
err= rows.Scan(&s)if err !=nil{
fmt.Println(err)
return
}
fmt.Println(s)
}
rows函数的使用方法及实例rows.Close()
其实当我们通过for循环迭代数据库的时候,当迭代到最后⼀样数据的时候,会出发⼀个io.EOF的信号,引发⼀个错误,同时go会⾃动调⽤rows.Close⽅法释放连接,然后返回false,此时循环将会结束退出。
通常你会正常迭代完数据然后退出循环。可是如果并没有正常的循环⽽因其他错误导致退出了循环。此时rows.Next处理结果集的过程并没有完成,归属于rows的连接不会被释放回到连接池。因此⼗分有必要正确的处理rows.Close事件。如果没有关闭rows连接,将导致⼤量的连接并且不会被其他函数重⽤,就像溢出了⼀样。最终将导致数据库⽆法使⽤。
所以为了避免这种情况的发⽣,最好的办法就是显⽰的调⽤rows.Close⽅法,确保连接释放,⼜或者使⽤defer指令在函数退出的时候释放连接,即使连接已经释放了,rows.Close仍然可以调⽤多次,是⽆害的。
rows.Next循环迭代的时候,因为触发了io.EOF⽽退出循环。为了检查是否是迭代正常退出还是异常退出,需要检查rows.Err。例如上⾯的代码应该改成:
//Query执⾏查询操作
rows,err := Db.Query("SELECT email FROM user_info WHERE user_id>=5")if err !=nil{
fmt.Println("select db failed,err:",err)
return
}// 这⾥获取的rows是从数据库查的满⾜user_id>=5的所有⾏的email信息,rows.Next(),⽤于循环获取所有
for rows.Next(){
var s string
err= rows.Scan(&s)if err !=nil{
fmt.Println(err)
return
}
fmt.Println(s)
}
rows.Close()if err = rows.Err();err !=nil{
fmt.Println(err)
return
}
读取单条数据
Query⽅法是读取多⾏结果集,实际开发中,很多查询只需要单条记录,不需要再通过Next迭代。golang提供了QueryRow⽅法⽤于查询单条记录的结果集。
QueryRow⽅法的使⽤很简单,它要么返回sql.Row类型,要么返回⼀个error,如果是发送了错误,则会延迟到Scan调⽤结束后返回,如果没有错误,则Scan正常执⾏。只有当查询的结果为空的时候,会触发⼀个sql.ErrNoRows错误。你可以选择先检查错误再调⽤Scan⽅法,或者先调⽤Scan再检查错误。
在之前的代码中我们都⽤到了Scan⽅法,下⾯说说关于这个⽅法
结果集⽅法Scan可以把数据库取出的字段值赋值给指定的数据结构。它的参数是⼀个空接⼝的切⽚,这就意味着可以传⼊任何值。通常把需要赋值的⽬标变量的指针当成参数传⼊,它能将数据库取出的值赋值到指针值对象上。
代码例⼦如:
//查询数据
var username string
var email string
rows := Db.QueryRow("SELECT username,email FROM user_info WHERE user_id=6")
err= rows.Scan(&username,&email)if err !=nil{
fmt.Println("scan err:",err)
return
}
fmt.Println(username,email)
Scan还会帮我们⾃动推断除数据字段匹配⽬标变量。⽐如有个数据库字段的类型是VARCHAR,⽽他的值是⼀个数字串,例如"1"。如果我们定义⽬标变量是string,则scan赋值后⽬标变量是数字string。如果声明的⽬标变量是⼀个数字类型,那么scan会⾃动调⽤
strconv.ParseInt()或者strconv.ParseInt()⽅法将字段转换成和声明的⽬标变量⼀致的类型。当然如果有些字段⽆法转换成功,则会返回错误。因此在调⽤scan后都需要检查错误。
空值处理
数据库有⼀个特殊的类型,NULL空值。可是NULL不能通过scan直接跟普遍变量赋值,甚⾄也不能将null赋值给nil。对于null必须指定特殊的类型,这些类型定义在database/sql库中。例如sql.NullFloat64,sql.NullString,sql.NullBool,sql.NullInt64。如果在标准库中不到匹配的类型,可以尝试在驱动中寻。下⾯是⼀个简单的例⼦:
下⾯代码,数据库中create_time为Null这个时候,如果直接这样查询,会提⽰错误:
//查询数据
var username string
var email string
var createTime string
rows := Db.QueryRow("SELECT username,email,create_time FROM user_info WHERE user_id=6")
err= rows.Scan(&username,&email,&createTime)if err !=nil{
fmt.Println("scan err:",err)
return
}
fmt.Println(username,email,createTime)
错误内容如下:
scan err: sql: Scan error on column index 2: unsupported Scan, storing driver.Value type into type *string
所以需要将代码更改为:
//查询数据
var username string
var email string
var createTime sql.NullString
rows := Db.QueryRow("SELECT username,email,create_time FROM user_info WHERE user_id=6")
err= rows.Scan(&username,&email,&createTime)if err !=nil{
fmt.Println("scan err:",err)
return
}
fmt.Println(username,email,createTime)
执⾏结果为:
user01 8989@qq { false}
我将数据库中添加了⼀列,是int类型,同样的默认值是Null,代码为:
//查询数据
var username string
var email string
var createTime string
var scoreintrows := Db.QueryRow("SELECT username,email,create_time,socre FROM user_info WHERE user_id=6")
rows.Scan(&username,&email,&createTime,&score)
fmt.Println(username,email,createTime,score)
其实但我们忽略错误直接输出的时候,也可以输出,当然Null的字段都被转换为了零值
⽽当我们按照上⾯的⽅式处理后,代码为:
//查询数据
var username string
var email string
var createTime sql.NullString
var score sql.NullInt64
rows := Db.QueryRow("SELECT username,email,create_time,socre FROM user_info WHERE user_id=6")
err= rows.Scan(&username,&email,&createTime,&score)if err !=nil{
fmt.Println("scan fail,err:",err)
return
}
fmt.Println(username,email,createTime,score)
输出的结果为:
user01 8989@qq { false} {0 false}
对Null的操作,⼀般还是需要验证的,代码如下:
//查询数据
var score sql.NullInt64
rows := Db.QueryRow("SELECT socre FROM user_info WHERE user_id=6")
err= rows.Scan(&score)if err !=nil{
fmt.Println("scan fail,err:",err)
return
}ifscore.Valid{
fmt.Println("res:",score.Int64)
}else{
fmt.Println("err",score.Int64)
}
这⾥我已经在数据库给字段添加内容了,所以这⾥默认输出10,但是当还是Null的时候输出的则是零值
但是有时候我们如果不关⼼是不是Null的时候,只是想把它当做空字符串处理就⾏,我们也可以使⽤[]byte,代码如下:
//查询数据
var score []bytevar modifyTime []byterows := Db.QueryRow("SELECT modify_time,socre FROM user_info WHERE user_id=6")
err= rows.Scan(&modifyTime,&score)if err !=nil{
fmt.Println("scan fail,err:",err)
return
}
fmt.Println(string(modifyTime),string(score))
这样处理后,如果有值则可以获取值,如果没有则获取的为空字符串
⾃动匹配字段
上⾯查询的例⼦中,我们都⾃⼰定义了变量,同时查询的时候也写明了字段,如果不指名字段,或者字段的顺序和查询的不⼀样,都有可能出错。因此如果能够⾃动匹配查询的字段值,将会⼗分节省代码,同时也易于维护。
go提供了Columns⽅法⽤获取字段名,与⼤多数函数⼀样,读取失败将会返回⼀个err,因此需要检查错误。
代码例⼦如下:
//查询数据
rows,err:= Db.Query("SELECT * FROM user_info WHERE user_id>6")if err !=nil{
fmt.Println("select fail,err:",err)
return
}
cols,err :=rows.Columns()if err !=nil{
fmt.Println("get columns fail,err:",err)
return
}
fmt.Println(cols)
vals := make([][]byte, len(cols))
scans :=make([]interface{},len(cols))
for i :=range vals{
scans[i]= &vals[i]
}
fmt.Println(scans)
var results []map[string]string
for rows.Next(){
err=rows.)if err !=nil{
fmt.Println("scan fail,err:",err)
return
}
row :=make(map[string]string)