MySQL 数据库

嗨,我是 Philipp!
我想告诉你,我的平台 Go Web 示例课程 刚刚推出。享受有关 Go 中 Web 开发的易于理解的视频课程。务必查看我为早期支持者提供的特别优惠。
我们在那里等你! :)
了解更多

简介

在某些时候,您希望您的 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 数据库。如果您还没有运行 MySQL 数据库服务器,您可以轻松地使用 Docker 启动一个新实例。以下是 Docker MySQL 映像的官方文档:https://hub.docker.com/_/mysql

要检查我们是否可以连接到数据库,请导入 database/sqlgo-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,它在只想要查询特定行时使用。

查询特定行基本上与我们之前介绍过的所有其他 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 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)
        }
    }
}