今天咱們繼續接着前幾篇關於Go Web
編程的文章往下延伸。在Web
應用程序中幾乎每一個應用場景都須要存儲和檢索數據庫中的數據。當你處理動態內容,爲用戶提供表單以輸入數據或存儲登陸名和密碼憑據以供用戶進行身份驗證時,都須要用到數據庫。 MySQL數據庫是整個互聯網中最經常使用的數據庫。MySQL已經存在了很長時間,還在不停的進化而且隨着互聯網一塊兒發展已屢次證實了其位置和穩定性。html
本文咱們將探究Go中數據庫訪問的基礎知識,在開始以前咱們先更新一下咱們使用的開發環境,以前在文章用Docker快速搭建Go開發環境 中咱們只應用了一個運行 go的容器,如今咱們爲開發環境加上數據庫。mysql
關注文末公衆號,回覆
gohttp04
獲取容器編排文件和文章中用到的源代碼。git
打開咱們以前編寫的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
後完整的編排文件就變成:github
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
編排文件和文章中用到的源代碼。golang
go-sql-driver/mysql
包Go語言標準庫中database / sql
包,用於查詢各類SQL
數據庫。它將全部通用SQL
功能抽象到一個API
中供開發者使用。 可是Go
的標準庫中不包括數據庫驅動程序。數據庫驅動程序由特定軟件包提供的,用於實現特定數據庫底層的封裝。這對於向前兼容頗有用,也使得Go
不會變的臃腫。由於在建立全部Go
軟件包時,開發人員沒法預見將來會有什麼數據庫會被投入使用,並且要支持每一個可能的數據庫將須要進行大量維護工做。web
使用下面命令安裝MySQL
驅動包:sql
go get -u github.com/go-sql-driver/mysql
複製代碼
要檢查咱們是否能夠鏈接到數據庫,咱們須要導入database/sql
和go-sql-driver/mysql
兩個包,並鏈接數據庫:docker
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
編排文件和文章中用到的源代碼。