这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记
之前学过一些MySQL数据库的基础知识,但还没在Go下使用过数据库,加上今天的课听得一脸茫然,所以这篇文章就简单记录一下使用Go操作MySQL数据库的过程。
下载数据库驱动
终端下载即可
go get -u github.com/go-sql-driver/mysql
导入数据库接口和数据库驱动
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
其中database/sql是一个通用的接口,Go语言官方没有提供数据库驱动,而是为开发数据库驱动定义了标准接口database/sql,开发者可以根据database/sql接口来开发相应的数据库驱动,只要是按照标准接口database/sql开发的代码,以后需要迁移数据库时,不需要任何修改。这就使得在切换数据库时无序更改Go程序。
在导入路径前加入下划线表示只执行该库的init函数,而其init函数如下,将MySQL的driver注册到了database/sql中:
func init() {
sql.Register("mysql", &MySQLDriver{})
}
访问数据库
dsn := "usename:password@tcp(127.0.0.1:3306)/fruitdb"
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
defer db.Close()
err = db.Ping()
if err != nil {
panic(err)
}
Open函数可能只是验证其参数格式是否正确,实际上并不创建与数据库的连接。第一个真正的连接直到被使用时才创建。如果要检查数据源的名称是否真实有效,应该调用Ping方法。
返回的DB对象可以安全地被多个goroutine并发使用,并且维护其自己的空闲连接池。因此,Open函数应该仅被调用一次,很少需要关闭这个DB对象。
成功访问数据库之后,就可以进行CRUD操作了。
查询
单行查询
sqlStr := "select * from t_fruit where fid = ?"
var f fruit
//单行查询
err = db.QueryRow(sqlStr, 7).Scan(&f.fid, &f.fname, &f.price, &f.fcount, &f.remark)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(f.fid, f.fname, f.price, f.fcount, f.remark)
}
多行查询
//多行查询
sqlStr = "select fid,fname,remark from t_fruit where fid > ?"
//迭代器
rows, err := db.Query(sqlStr, 4)
if err != nil {
fmt.Println(err)
}
//关闭rows持有的数据库连接
defer rows.Close()
for rows.Next() {
var f2 fruit
err := rows.Scan(&f2.fid, &f2.fname, &f2.remark)
if err != nil {
fmt.Println(err)
}
fmt.Println(f2.fid, f2.fname, f2.remark)
}
增 Create
需要事先定义一个结构体
type fruit struct {
fid int
fname string
price int
fcount int
remark string
}
不涉及返回行的操作使用Exec()函数
sqlStr = "insert into t_fruit values (?,?,?,?,?)"
ret, err := db.Exec(sqlStr, 99, "apple", 98, 97, "aaaa")
if err != nil {
fmt.Println(err)
}
//新插入数据的id
newId, err := ret.LastInsertId()
if err != nil {
fmt.Println(err)
}
fmt.Println(newId)
改 Update
sqlStr = "update t_fruit set remark = ? where fid = ?"
ret, err := db.Exec(sqlStr, "abcd", 99)
if err != nil {
fmt.Println(err)
}
//返回操作影响的行数
n, err := ret.RowsAffected()
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
删 Delete
sqlStr = "delete from t_fruit where fid = ?"
ret, err = db.Exec(sqlStr, 99)
if err != nil {
fmt.Println(err)
}
n, err = ret.RowsAffected()
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
到这里,基本的数据库操作就完成了。
MySql的预处理
- 什么是预处理?预处理的过程?
- 把SQL语句分成两部分,命令部分与数据部分。
- 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
- 再把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
- 预处理的作用?
- 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
- 避免SQL注入问题。 Prepare()方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。
//预处理
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Println(err)
}
defer stmt.Close()
rows, err = stmt.Query(8)
if err != nil {
fmt.Println(err)
}
defer rows.Close()
for rows.Next() {
var f3 fruit
err := rows.Scan(&f3.fid, f3.fname, f3.fcount)
if err != nil {
fmt.Println(err)
}
fmt.Println(f3.fid, f3.fname, f3.fcount)
}
同时,任何时候都不应该自己拼接SQl语句,会引发SQl注入问题。
而关于这堂课讲的其他内容,由于听的是一脸懵,暂时还在翻阅资料,以后有了更深的认识后会补充。