【Golang】快速複習指南QuickReview(十一)——數據庫訪問(MySql爲例)


對於業務層面的開發,就離不開數據庫的訪問。

1.創建項目

俗話說賣錢不賣錢,攤攤兒要扯圓,甭管怎樣,我們先建立一個標準的golang項目,來訪問數據庫。

1.1 go mod 管理依賴

go mod init gitee.com/RandyField/sqltest

1.2 安裝mysql驅動包

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

1.3 創建文件

cd sqltest
New-Item main.go  
New-Item service.go #數據庫訪問方法
New-Item models.go  #數據映射結構

2.連接數據庫

main.go

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

const (
	user      = "root"
	password  = "111111"
	database  = "push-center"
	protocol  = "tcp"
	address   = "127.0.0.1"
	charset   = "utf8mb4"
	parseTime = "True"
)

var db *sql.DB

func main() {
	/*
		https://pkg.go.dev/github.com/go-sql-driver/mysql
		https://github.com/denisenkom/go-mssqldb
		gorm-sqlx
	*/
	// Connstr := fmt.Sprintf("%s:%s@%s(%s)/%s?charset=%s&parseTime=%s",
	// 	user, password, protocol, address, database, charset, parseTime)
	// fmt.Println(Connstr)
	// Connstr := "root:111111@tcp(127.0.0.1)/push-center?charset=utf8mb4&parseTime=True"

	//parseTime 是查詢結果是否自動解析爲時間
	//loc 是MySQL的時區設置
	Connstr := "root:111111@tcp(127.0.0.1)/push-center?charset=utf8mb4&parseTime=True&loc=Local"
	fmt.Println("start...")
	var err error
	db, err = sql.Open("mysql", Connstr)
	if err != nil {
		log.Fatalln(err.Error())
	}

	ctx := context.Background()
	//驗證連接是否有效
	err = db.PingContext(ctx)
	if err != nil {
		log.Fatalf(err.Error())
	}
	fmt.Println("Connected")
}
  • import "database/sql"
    • Golangdatabase/sql包提供了保證SQL或類SQL數據庫的泛用接口。我們的數據庫操作(編碼)也只在database/sql包上進行。
  • import _ "github.com/go-sql-driver/mysql"
    • 連接數據庫,需要加載目標數據庫的驅動,Golang是沒有提供官方的數據庫驅動,所有的數據庫驅動都是第三方驅動,但是它們都遵循sql.driver包裏面定義的接口
    • 我們並直接使用這個驅動,所以使用_引入,只需要在引入驅動包時調用包內init函數進行自動註冊。

實際上,需要使用sql包的Register()

  • 數據庫驅動名稱

  • 並實現driver.Driver()接口的struct

  • 註冊:sql.Register("mysql",&drv{})

  • Connstr:username/password@tcp(ipaddress)/database?parm1=&parm2=

    • 連接字符串,尤其注意後面的參數,博主在這裏使用.netEFCore插入時間,值總是正確,而在使用golang時卻總是有問題(晚8小時),無論在代碼層面做何種轉換。
      • parseTime是查詢結果是否自動解析爲時間
      • loc是MySQL的時區設置
  • sql.Open():僅僅是配置連接,但並不真正連接,需要兩個參數:

    • 數據庫驅動名稱
    • 數據庫連接字符串
    • 返回一個執行sql.DB這個struct的指針:*sql.DB

這個指針纔是我們操作數據庫的關鍵鑰匙,我們編碼通過這個指針發送sql命令,獲得結果。它抽象了底層數據庫連接池並對其維護,且併發安全,這便意味着我們可以在多個goroutine中併發使用。針對*sql.DB有兩種用法:

  • 定義全局變量,然後到處使用
  • 定義變量,將其作爲參數傳遞給函數或者方法
  • ctx := context.Background():Context(上下文)類型可以攜帶截止時間、取消信號和其他請求範圍的值,並且可以橫跨API邊界和進程。但是這裏的context包的Background()返回的Context很特殊,非nil的空Context,不會被取消也沒有值,沒有截止時間。

    • 通常用在main函數、初始化或測試中,作爲傳入請求的頂級Context
  • db.PingContext(ctx):驗證與數據庫的連接是否仍然有效,如有必要則建立一個連接。

3.訪問數據庫

訪問之前我們需要能夠映射數據庫表的struct,但是struct非必需條件。

models.go

package main

//Notifypush 推送通知
type notifypush struct {
	Id          int    `json:"Id"`
	AppName     string `json:"APP"`
	Target      int    `json:"Platform"`
	TargetValue *string
	PushType    int
	DeviceType  int `json:"Device"`
	Title       *string
	Body        *string `json:"Content"`
	CreateTime  string
}

如果數據庫字段有Null,可空類型, 結構體或者變量,都需要定義指針類型,否則會發生運行時錯誤。

3.1 查詢單條

service.go

package main

import (
	"log"
	"time"
)

// GetById 根據ID獲取單條數據
func GetById(id int) (notifypush, error) {
	n := notifypush{}
	err := db.QueryRow("select Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notifypush where Id=?",
		id).Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
	// err := db.QueryRow("select Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notitypush where Id=@Id",
	// 	sql.Named("Id", id)).Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
	return n, err
}

如果是sqlserver,參數是使用@+參數名來進行站位,並配合sql.Named()函數使用。mysql不能這樣,否則會報錯mysql: driver does not support the use of Named Parameters

3.2 查詢多條

// GetMultiRow 獲取多條數據
func GetMultiRow(id int) (ns []notifypush, err error) {
	rows, err := db.Query("SELECT Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notifypush WHERE Id>?",
		id)
	// 非常重要:關閉rows釋放持有的數據庫鏈接
	defer rows.Close()

	for rows.Next() {
		n := notifypush{}
		err = rows.Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
		if err != nil {
			log.Fatalln(err.Error())
		}
		ns = append(ns, n)
	}
	return
}

3.2 修改、插入、刪除

修改、插入、刪除都是cmd,在sql包中只有一個方法:Exec,這裏就省略delete操作,實際業務上很少使用物理刪除。

Update

Update操作,需要定義方法(結構體爲接收者)

//Update 更新
func (push *notifypush) Update() error {
	_, err := db.Exec("UPDATE notifypush SET AppName=?,Target=?,PushType=?,DeviceType=? WHERE Id=?",
		push.AppName, push.Target, push.PushType, push.DeviceType, push.Id)
	return err
}

Insert

//Insert 插入
func Insert() error {
	// _, err := db.Exec("INSERT INTO  notifypush(AppName,Target,PushType,DeviceType,CreateTime) VALUES(?,?,?,?,?)",
	// 	"test-insert-app", 1, 2, 3, time.Now())

	// 改用 prepare
	sql := `INSERT INTO  notifypush(AppName,Target,PushType,DeviceType,CreateTime) VALUES(?,?,?,?,?)`
	stmt, err := db.Prepare(sql)
	if err != nil {
		log.Fatalln(err.Error())
	}
	defer stmt.Close()
	_, err = stmt.Exec("insert-app", 1, 2, 3, time.Now())
	return err
}

4.調用方法

4.1 編碼

main.go

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

const (
	user      = "root"
	password  = "111111"
	database  = "push-center"
	protocol  = "tcp"
	address   = "127.0.0.1"
	charset   = "utf8mb4"
	parseTime = "True"
)

var db *sql.DB

func main() {
	//ommit connect code
    
    //查詢
    notify, err := GetById(10)
	if err != nil {
		log.Fatalf(err.Error())
	}
	data, err := json.Marshal(notify)
	fmt.Printf("推送消息爲:%s\n", data)
    
    //更新
    notify.AppName = "smart"
	err = notify.Update()
	if err != nil {
		log.Fatalf(err.Error())
	}
    notify, err = GetById(10)
	if err != nil {
		log.Fatalf(err.Error())
	}
	data, err = json.Marshal(notify)
	fmt.Printf("更新後:%s\n", data)
    
    //插入
    err = Insert()
	if err != nil {
		log.Fatalf(err.Error())
	}
    
    //查詢多條
    ns, err := GetMultiRow(24)
	datas, err := json.Marshal(ns)
	fmt.Printf("推送消息爲:%s\n", datas)
}

4.2 運行

這裏我們的項目是一個具有多個文件.gomodule,所以不能簡單使用go run main.go。需要先編譯,才能運行

go build #編譯會生成sqltest.exe
.\sqltest.exe #運行

5.ORM

5.1 GORM

GORMGoLang中最出色的ORM框架,支持MySQLPostgreSQLSqliteSQL Server,功能非常強大,也可以直接執行SQL並獲取結果集。還有數據庫遷移。博主把他看作Golang版本的EntityFramework

5.2 Sqlx

Sqlx是對GoLang標準database/sql的擴展。其特點是:

  1. 把SQL執行的結果集轉化成數據結構(StructMapsSlices)。
  2. 支持問號(?)或命名的Prepared Statements,避免SQL注入的安全問題

在博主看來,這個更像是一個golang版本的dapper

參考連接

https://www.bilibili.com/video/BV1dZ4y1577v

https://qinzhiqiang.cn/2019/10/golang服務常用組件-gorm-sqlx-mysql-mongodb/


作者:Garfield

同步更新至個人博客:http://www.randyfield.cn/

本文版權歸作者和博客園共有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯繫[email protected].

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章