Go Web 程式設計--應用資料庫

KevinYan發表於2020-02-13

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

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

關注文末公眾號,回覆gohttp04 獲取容器編排檔案和文章中用到的原始碼。

在開發環境中增加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/index.html,不過它是一個相對偏底層的庫。實際開發中往往會使用一些在它的基礎上封裝的`ORM`庫。`ORM`的查詢使用起來更簡單些,語法表達力更強也更方便於程式碼管理。所以今天的文章主要是對`database/sql`做一下簡單介紹,入門即可,後續關於`ORM`庫的使用時再介紹更多查詢的使用方法。另外也在我們的`Docker`環境中增加了 MySQL容器,大家也不要忘記更新。

公眾號回覆gohttp04獲取更新後的Docker編排檔案和文章中用到的原始碼。

前文回顧

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

Web伺服器路由

用Docker快速搭建Go開發環境

十分鐘學會用Go編寫Web中介軟體

本作品採用《CC 協議》,轉載必須註明作者和本文連結

公眾號:網管叨bi叨 | Golang、PHP、Laravel、Docker等學習經驗分享

相關文章