Go操作数据库基础 | 青训营笔记

241 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第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的预处理

  • 什么是预处理?预处理的过程?
  1. 把SQL语句分成两部分,命令部分与数据部分。
  2. 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
  3. 再把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
  4. MySQL服务端执行完整的SQL语句并将结果返回给客户端。
  • 预处理的作用?
  1. 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
  2. 避免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注入问题。

而关于这堂课讲的其他内容,由于听的是一脸懵,暂时还在翻阅资料,以后有了更深的认识后会补充。