今天我們繼續接着前幾篇關於 Go Web 編程的文章往下延伸。在 Web 應用程序中幾乎每個應用場景都需要存儲和檢索數據庫中的數據。當你處理動態內容,爲用戶提供表單以輸入數據或存儲登錄名和密碼憑據以供用戶進行身份驗證時,都需要用到數據庫。 MySQL數據庫是整個互聯網中最常用的數據庫。MySQL已經存在了很長時間,還在不停的進化並且隨着互聯網一起發展已多次證明了其位置和穩定性。

本文我們將探究Go中數據庫訪問的基礎知識,在開始之前我們先更新一下我們使用的開發環境,之前在文章 用Docker快速搭建Go開發環境 中我們只應用了一個運行 go的容器,現在我們爲開發環境加上數據庫。

在開發環境中增加MySQL容器

打開我們之前編寫的 docker-compose.yml 文件,添加如下配置:

version: '3'
services:
  ......
  # The Database
  database:
    image: mysql:5.7
    volumes:
      - dbdata:/var/lib/mysql
    environment:
      - "MYSQL_DATABASE=go_web"
      - "MYSQL_USER=go_web"
      - "MYSQL_PASSWORD=go_web"
      - "MYSQL_ROOT_PASSWORD=secret"
    ports:
      - "33063:3306"

volumes:
  dbdata:
  • 因爲容器退出時會銷燬容器內的所有文件,所以對於 MySQL 這種存儲持久化數據的容器需要與外部宿主機做文件映射,這樣再次啓動 MySQL 容器後就會從數據映射中讀取之前的數據。
  • 在編排文件的中我們通過 volumes 命令創建了一個名爲 dbdata 的數據卷( dbdata 後面的冒號是有意寫上去的,這是 YML 文件的一個語法限制,不用太關心)
  • 定義完數據卷後,在上面我們使用 dbdata:/var/lib/mysql 的格式,通知 Docker,將 dbdata 數據卷掛在到容器中的 /var/lib/mysql 目錄上。
  • environments 中設置的是 MySQL 容器需要的四個必要參數。
  • ports 端口映射中,我們將本地電腦的 33063 端口映射到容器的 3306 端口,這樣我們就能通過電腦上的數據庫工具連接到 容器內的 MySQL 了。

添加完 MySQL 後完整的編排文件就變成:

version: '3'
services:
  # The Application
  app:
    image: golang:latest
    working_dir: /go/src/example.com/http_demo
    volumes:
      - /$GOPATH/src/example.com/http_demo:/go/src/code.qschou.com/http_demo
      - /$GOPATH/src:/go/src
    ports:
      - "8000:8080"
    environment:
      WORKING_DIR: /go/src/e.qschou.com/practice/http_demo
    command: go run /go/src/example.com/http_demo/main.go

  # The Database
  database:
    image: mysql:5.7
    volumes:
      - dbdata:/var/lib/mysql
    environment:
      - "MYSQL_DATABASE=go_web"
      - "MYSQL_USER=go_web"
      - "MYSQL_PASSWORD=go_web"
      - "MYSQL_ROOT_PASSWORD=secret"
    ports:
      - "33063:3306"

volumes:
  dbdata:

可以公衆號內發送 gohttp04 獲取 Docker 編排文件和文章中用到的源代碼。

安裝 go-sql-driver/mysql

Go語言標準庫中 database / sql 包,用於查詢各種 SQL 數據庫。它將所有通用 SQL 功能抽象到一個 API 中供開發者使用。 但是 Go 的標準庫中不包括數據庫驅動程序。數據庫驅動程序由特定軟件包提供的,用於實現特定數據庫底層的封裝。這對於向前兼容很有用,也使得 Go 不會變的臃腫。因爲在創建所有 Go 軟件包時,開發人員無法預見未來會有什麼數據庫會被投入使用,而且要支持每個可能的數據庫將需要進行大量維護工作。

使用下面命令安裝 MySQL 驅動包:

go get -u github.com/go-sql-driver/mysql

連接MySQL數據庫

要檢查我們是否可以連接到數據庫,我們需要導入 database/sqlgo-sql-driver/mysql 兩個包,並連接數據庫:

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

func MysqlDemoCode() {

    db, err := sql.Open("mysql", "go_web:go_web@tcp(127.0.0.1:3306)/go_web")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    if err := db.Ping(); err != nil {
        log.Fatal(err)
    }
}
  • 你的代碼應僅引用在 database/sql 中定義的類型和函數。這有助於避免使代碼依賴於特定驅動程序,從而使你可以通過最少的代碼更改來更改使用的數據庫驅動(相應也會更改使用的數據庫類型)。
  • 代碼中對驅動包使用匿名包導入,將 go-sql-driver/mysql 的包別名設置爲 _ ,這樣驅動程序導出的名稱對我們的代碼都是不可見的,但是在幕後 go-sql-driver/mysql 將自身註冊爲可用於 database/sql 的驅動包。一般而言,除了運行包的 init 函數外,不會發生任何其他事情。
  • sql.Open() 不會建立與數據庫的任何連接,也不會驗證驅動程序的連接參數。它只是返回抽象數據庫的對象以供後面使用。數據庫連接在真正需要訪問數據庫的時候纔會建立。

我們可以通過單元測試驗證數據庫是否能正確連接上,測試代碼我就不貼了,可以通過文章的源碼包裏看到,唯一提醒一點,如果在本地機器裏運行測試需要把上面 sql.Open() 配置的端口改爲 33063

創建表

我們接下來創建一個像這樣的表:

id username password created_at
1 Joshua secret 2020-02-13 12:30:00

使用 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)
   );`
// 執行後一定要檢查err
_, err := db.Exec(query)

插入新數據

默認情況下,Go使用準備好的語句(prepare)將動態數據插入到我們的SQL語句中,這是一種將用戶提供的數據安全地傳遞到我們的數據庫而不會造成任何損壞的方式。在Web編程的早期,程序員將數據和查詢直接傳遞給數據庫,這導致了巨大的漏洞,並可能破壞整個Web應用程序。

要將我們的第一個用戶插入數據庫表,我們將創建一個如下的SQL查詢。語句中的問號告訴SQL驅動程序,它們是實際數據的佔位符。下面你可以看到我們討論的準備好的語句:

username := "johndoe"
password := "secret"
createdAt := time.Now()

result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)

結果包含最後插入的ID(自增ID)的信息以及此查詢影響的行數。

// 獲取新插入數據庫的用戶ID
userID, err := result.LastInsertId()

查詢表數據

現在我們的表中有一個用戶,我們想要查詢它並獲取其所有信息。使用 database/sql 包我們有兩種查詢表的方式。 db.Query 可以查詢多行,以便我們進行迭代; db.QueryRow 查詢特定的行。

查詢單行

我們首先聲明一些變量來存儲數據,然後查詢單個數據庫行:

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)

查詢多行

上面我們演示瞭如何查詢單個用戶行, 接下來演示下如何查詢多個數據行並將數據存儲到結構體切片中:

type user struct {
    id        int
    username  string
    password  string
    createdAt time.Time
}

rows, err := db.Query(`SELECT id, username, password, created_at FROM users`) //檢查錯誤
defer rows.Close()

var users []user
for rows.Next() {
    var u user
    err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt) 
    users = append(users, u)
}
err := rows.Err() //檢查錯誤

篇幅原因代碼中所有的錯誤檢查都被故意忽略了,在實際使用中一定要記得做錯誤檢查。

users 切片中存儲的數據類似這樣:

users {
    user {
        id:        1,
        username:  "Joshua",
        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

result, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1) // 記得檢查錯誤

後續

database/sql 提供的 MySQL 查詢功能還是很全面的使用的更多介紹可以參考 http://go-database-sql.org/in... ,不過它是一個相對偏底層的庫。實際開發中往往會使用一些在它的基礎上封裝的 ORM 庫。 ORM 的查詢使用起來更簡單些,語法表達力更強也更方便於代碼管理。所以今天的文章主要是對 database/sql 做一下簡單介紹,入門即可,後續關於 ORM 庫的使用時再介紹更多查詢的使用方法。另外也在我們的 Docker 環境中增加了 MySQL 容器,大家也不要忘記更新。

公衆號回覆 gohttp04 獲取更新後的 Docker 編排文件和文章中用到的源代碼。

前文回顧

深入學習用Go編寫HTTP服務器

Web服務器路由

用Docker快速搭建Go開發環境

十分鐘學會用Go編寫Web中間件

相關文章