Go网络编程-MySQL Database
简介
在某个时刻,你的Web应用程序需要存储和检索数据库中的数据。当你处理动态内容、为用户提供输入数据的表单或存储登录和密码凭据以对用户进行身份验证时,这几乎总是必要的。为此,我们有了数据库。
数据库有各种形式和形状。在整个网络中,一个常用的数据库是MySQL数据库。它已经存在很长时间,并且多次证明了其地位和稳定性。
在这个例子中,我们将深入探讨Go中数据库访问的基础知识,创建数据库表,存储数据并再次检索数据。
安装go-sql-driver/mysql包
Go编程语言附带了一个名为database/sql
的方便包,用于查询各种SQL数据库。这很有用,因为它将所有常见的SQL功能抽象为单个API供你使用。Go没有包含的是数据库驱动程序。在Go中,数据库驱动程序是一个实现了特定数据库(在我们的案例中是MySQL)的底层细节的包。你可能已经猜到了,这有助于保持向前兼容性。因为在创建所有Go包的时候,作者无法预见到将来会出现的每一个数据库,支持所有可能的数据库将是一项大量的维护工作。
要安装MySQL数据库驱动程序,请转到你选择的终端并运行:
go get -u github.com/go-sql-driver/mysql
连接到MySQL数据库
安装所有必要的包后,我们需要检查的第一件事是,我们是否能够成功连接到我们的MySQL数据库。如果你还没有运行MySQL数据库服务器,你可以轻松地使用Docker启动一个新实例。这里是Docker MySQL镜像的官方文档:https://hub.docker.com/_/mysql
要检查我们是否能够连接到我们的数据库,导入database/sql和go-sql-driver/mysql包并像这样打开一个连接:
import "database/sql"
import _ "go-sql-driver/mysql"
// Configure the database connection (always check errors)
db, err := sql.Open("mysql", "username:password@(127.0.0.1:3306)/dbname?parseTime=true")
// Initialize the first connection to the database, to see if everything works correctly.
// Make sure to check the error.
err := db.Ping()
创建我们的第一个数据库表
在我们的数据库中,每个数据条目都存储在特定的表中。一个数据库表由列和行组成。列给每个数据条目一个标签并指定其类型。行是插入的数据值。在第一个示例中,我们希望创建一个如下所示的表:
id | username | password | created_at |
---|---|---|---|
1 | johndoe | secret | 2019-08-10 12:30:00 |
用SQL表示,创建表的命令将如下所示:
CREATE TABLE users (
id INT AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (id)
);
现在我们已经得到了我们的SQL命令,我们可以使用database/sql包在我们的MySQL数据库中创建表:
query := `
CREATE TABLE users (
id INT AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (id)
);`
// 在数据库中执行SQL查询。检查err以确保没有错误。
_, err := db.Exec(query)
插入我们的第一个用户
如果您熟悉SQL,向表中插入新数据就像创建我们的表一样简单。需要注意的是:Go默认使用预处理语句为我们的SQL查询插入动态数据,这是一种安全地将用户提供的数据传递给我们的数据库的方法,而无需任何风险。在Web编程的早期,程序员直接将数据与查询一起传递给数据库,这导致了严重的漏洞,并可能导致整个Web应用程序崩溃。请不要这样做。
要将第一个用户插入到我们的数据库表中,我们创建一个如下所示的SQL查询。如您所见,我们省略了id列,因为它由MySQL自动设置。问号告诉SQL驱动程序,它们是实际数据的占位符。这就是我们之前讨论过的预处理语句。
INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)
我们现在可以在Go中使用这个SQL查询并将一行插入到我们的表中:
import "time"
username := "johndoe"
password := "secret"
createdAt := time.Now()
// 将我们的数据插入到users表中,并返回结果和可能的错误。
// 结果包含有关最后插入的id(它为我们自动生成)和此查询影响的行数的信息。
result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)
要获取新创建的用户ID,只需像这样获取:
userID, err := result.LastInsertId()
查询我们的用户表
现在我们已经表中有一个用户,我们想要查询它并获取所有信息。在Go中,我们有两种可能性来查询我们的表。有db.Query可以查询多行,供我们迭代,以及db.QueryRow如果我们只想查询特定的行。
查询特定行的工作方式基本上与其他我们介绍过的SQL命令相同。
我们通过ID查询单个用户的SQL命令如下所示:
SELECT id, username, password, created_at FROM users WHERE id = ?
在Go中,我们首先声明一些变量来存储我们的数据,然后像下面这样查询单个数据库行:
var (
id int
username string
password string
createdAt time.Time
)
// 查询数据库并将值扫描到我们的变量中。不要忘记检查错误。
query := `SELECT id, username, password, created_at FROM users WHERE id = ?`
err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt)
查询所有用户
在上述部分中,我们介绍了如何查询单个用户行。许多应用程序都有使用案例,您希望查询所有现有用户。这与上面的示例类似,但涉及更多的编码。
我们可以使用上面示例中的SQL命令,并去掉WHERE子句。这样,我们就可以查询所有现有用户。
SELECT id, username, password, created_at FROM users
在Go中,我们首先声明一些变量来存储我们的数据,然后像下面这样查询单个数据库行:
type user struct {
id int
username string
password string
createdAt time.Time
}
rows, err := db.Query(`SELECT id, username, password, created_at FROM users`) // check err
defer rows.Close()
var users []user
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt) // check err
users = append(users, u)
}
err := rows.Err() // check err
users切片现在可能包含以下内容:
users {
user {
id: 1,
username: "johndoe",
password: "secret",
createdAt: time.Time{wall: 0x0, ext: 63701044325, loc: (*time.Location)(nil)},
},
user {
id: 2,
username: "alice",
password: "bob",
createdAt: time.Time{wall: 0x0, ext: 63701044622, loc: (*time.Location)(nil)},
},
}
从我们的表中删除一个用户
最后,从我们的表中删除一个用户就像我们在上面的示例中使用的.Exec一样简单:
_, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1) // check err
完整代码
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "root:root@(127.0.0.1:3306)/root?parseTime=true")
if err != nil {
log.Fatal(err)
}
if err := db.Ping(); err != nil {
log.Fatal(err)
}
{ // Create a new table
query := `
CREATE TABLE users (
id INT AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (id)
);`
if _, err := db.Exec(query); err != nil {
log.Fatal(err)
}
}
{ // Insert a new user
username := "johndoe"
password := "secret"
createdAt := time.Now()
result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)
if err != nil {
log.Fatal(err)
}
id, err := result.LastInsertId()
fmt.Println(id)
}
{ // Query a single user
var (
id int
username string
password string
createdAt time.Time
)
query := "SELECT id, username, password, created_at FROM users WHERE id = ?"
if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {
log.Fatal(err)
}
fmt.Println(id, username, password, createdAt)
}
{ // Query all users
type user struct {
id int
username string
password string
createdAt time.Time
}
rows, err := db.Query(`SELECT id, username, password, created_at FROM users`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var users []user
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt)
if err != nil {
log.Fatal(err)
}
users = append(users, u)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("%#v", users)
}
{
_, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1)
if err != nil {
log.Fatal(err)
}
}
}