在某些时候,您希望您的 Web 应用程序从数据库存储和检索数据。当您处理动态内容、为用户提供输入数据的表单或存储用户登录和密码凭据以对其进行身份验证时,几乎总是这种情况。为此,我们有数据库。
数据库有各种形式和形状。整个 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 数据库服务器,您可以轻松地使用 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)
);`
// Executes the SQL query in our database. Check err to ensure there was no error.
_, err := db.Exec(query)
如果您熟悉 SQL,将新数据插入我们的表与创建表一样容易。需要注意的一件事是:默认情况下,Go 使用预处理语句将动态数据插入我们的 SQL 查询中,这是一种将用户提供的数据安全地传递到我们的数据库而不会造成任何损坏风险的方法。在网络编程的早期,程序员直接使用查询将数据传递到数据库,这导致了大规模的漏洞,并可能破坏整个网络应用程序。请不要这样做。做到正确很容易。
要将我们的第一个用户插入到数据库表中,我们创建一个类似于以下内容的 SQL 查询。如您所见,我们省略了 id 列,因为它由 MySQL 自动设置。问号告诉 SQL 驱动程序,它们是实际数据的占位符。这是您可以看到我们讨论的预处理语句的地方。INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)
我们现在可以在 Go 中使用此 SQL 查询,并在表中插入新行
import "time"
username := "johndoe"
password := "secret"
createdAt := time.Now()
// Inserts our data into the users table and returns with the result and a possible error.
// The result contains information about the last inserted id (which was auto-generated for us) and the count of rows this query affected.
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
,它在只想要查询特定行时使用。
SELECT id, username, password, created_at FROM users WHERE id = ?
在 Go 中,我们首先声明一些变量来存储我们的数据,然后像这样查询单个数据库行
var (
id int
username string
password string
createdAt time.Time
)
// Query the database and scan the values into out variables. Don't forget to check for errors.
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)
}
}
}