Go 資料庫編程
一、連接資料庫
準備連接到資料庫
- 要想連接到 SQL 資料庫,首先需要加載目標資料庫的驅動,驅動里面包含著于該資料庫互動的邏輯,
- sql.Open()
- 資料庫驅動的名稱
- 資料源名稱
- 得到一個指向 sql.DB 這個 struct 的指標
- sql.DB 是用來操作資料庫的,它代表了0個或者多個底層連接的池,這些連接由sql 包來維護,sql 包會自動的創建和釋放這些連接
- 它對于多個 goroutine 并發的使用是安全的
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/denisenkom/go-mssqldb"
)
var db *sql.DB
const (
server = "xxxx.database.windows.net"
port = 1433
user = "xxxxx"
password = "xxxxx"
database = "go-db"
)
func main() {
connStr := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;database=%s;",
server, user, password, port, database)
db, err := sql.Open("sqlserver", connStr)
if err != nil {
log.Fataln(err.Error())
}
ctx := context.Background()
err = db.PingContext(ctx)
if err != nil {
log.Fataln(err.Error())
}
fmt.Println("Connected!")
}
Note
- Open() 函式并不會連接資料庫,甚至不會驗證其引數,它只是把后續連接到資料庫所必需的 structs 給設定好了
- 而真正的連接是在被需要的時候才進行懶設定的
- sql.DB 不需要進行關閉(當然你想關閉也是可以的)
- 它就是用來處理資料庫的,而不是實際的連接
- 這個抽象包含了資料庫連接的池,而且會對此進行維護
- 在使用 sql.DB 的時候,可以定義它的全域變數進行使用,也可以將它傳遞到函式/方法里,
如何獲得驅動
- 正常的做法是使用 sql.Register() 函式、資料庫驅動的名稱和一個實作了 driver.Driver 介面的 struct,來注冊資料庫的驅動,例如:
- sql.Register("sqlserver", &drv{})
- 但是我們之前的例子卻沒寫這句話,為什么?
- 因為 Sql Server 的驅動,是在這個包被引入的時候進行了自我注冊
驅動自動注冊
- 當 go-mssqldb 包被引入的時候,它的 init 函式將會運行并進行自我注冊(在 Go 語言里,每個包的 init 函式都會在自動的呼叫)
- 在引入 go-mssqldb 包的時候,把該包的名設定為下劃線 _,這是因為我們不直接使用資料庫驅動(只需要它的”副作用“),我們只使用 database/sql
- 這樣,如果未來升級驅動,也無需改變代碼
- Go 語言沒有提供官方的資料庫驅動,所有的資料庫驅動都是第三方驅動,但是它們都遵循 sql.driver 包里面定義的介面
安裝資料庫驅動
- 這是安裝 Microsoft SQL Server 資料庫驅動的例子:
- go get github.com/denisenkom/go-mssqldb
func(*DB) PingContext
- 上例中的 db.PingContext() 函式是用來驗證與資料庫的連接是否仍然有效,如有必要則建立一個連接,
- 這個函式需要一個 Context (背景關系)型別的引數,這種型別可以攜帶截止時間、取消信號和其它請求范圍的值,并且可以橫跨 API 邊界和行程,
- 上例中,創建 context 使用的是 context.Background() 函式,該函式回傳一個非 nil 的空 Context,它不會被取消,它沒有值,沒有截止時間,
- 它通常用在 main 函式、初始化或測驗中,作為傳入請求的頂級 Context,
Exercises
- 使用 PostgreSQL 建立資料庫,使用 Go 語言進行連接,并 Ping 一下,
- 使用 SQLite 建立資料庫,使用 Go 語言進行連接,并 Ping 一下,
連接MySQL
https://github.com/go-sql-driver/mysql
go get -u github.com/go-sql-driver/mysql
創建目錄
? mcd go_sql_demo
Code/go/go_sql_demo via ?? v1.20.3 via ?? base
? go mod init go_sql_demo
go: creating new go.mod: module go_sql_demo
Code/go/go_sql_demo via ?? v1.20.3 via ?? base
? c
Code/go/go_sql_demo via ?? v1.20.3 via ?? base
?
main.go
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
// 定義一個全域物件db
var db *sql.DB
// 定義一個初始化資料庫的函式
func initDB() (err error) {
// DSN:Data Source Name
dsn := "root:12345678@tcp(127.0.0.1:3306)/db_xuanke?charset=utf8mb4&parseTime=True"
// 不會校驗賬號密碼是否正確
// 注意!!!這里不要使用:=,我們是給全域變數賦值,然后在main函式中使用全域變數db
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
// 嘗試與資料庫建立連接(校驗dsn是否正確)
err = db.Ping()
if err != nil {
return err
}
return nil
}
func main() {
err := initDB() // 呼叫輸出化資料庫的函式
if err != nil {
fmt.Printf("init db failed,err:%v\n", err)
return
}
fmt.Println("connect to database")
}
二、CRUD
查詢
- sql.DB 型別上用于查詢的方法有:
- Query
- QueryRow
- QueryContext
- QueryRowContext
Query
- 回傳的型別是:type Rows struct {}
- Rows 的方法:
func (rs *Rows) Close() errorfunc (rs *Rows) ColumnTypes() ([]*ColumnType, error)func (rs *Rows) Columns() ([]string, error)func (rs *Rows) Err() errorfunc (rs *Rows) Next() boolfunc (rs *Rows) NextResultSet() boolfunc (rs *Rows) Scan(dest ...interface{}) error
QueryRow
- 回傳型別是:type Row struct {}
- Row 的方法有:
func (r *Row) Err() errorfunc (r *Row) Scan(dest ...interface{}) error
https://pkg.go.dev/database/[email protected]
services.go 檔案
package main
func getOne(id int) (a app, err error) {
a = app{}
log.Println(db == nil)
err = db.QueryRow("SELECT Id, Name, Status, Level, [Order] FROM dbo.App WHERE Id=@Id", sql.Named("Id", id)).Scan(
&a.ID, &a.name, &a.status, &a.level, &a.order)
return
}
func getMany(id int) (apps []app, err error) {
rows, err = db.Query("SELECT Id, Name, Status, Level, [Order] FROM dbo.App WHERE Id>@Id", sql.Named("Id", id))
for rows.Next() {
a := app{}
err = rows.Scan(&a.ID, &a.name, &a.status, &a.level, &a.order)
if err != nil {
log.Fatalln(err.Error())
}
apps = append(apps, a)
}
return
}
models.go 檔案
package main
type app struct {
ID int
name string
status int
level int
order int
}
main.go 檔案
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/denisenkom/go-mssqldb"
)
var db *sql.DB
const (
server = "xxxx.database.windows.net"
port = 1433
user = "xxxxx"
password = "xxxxx"
database = "go-db"
)
func main() {
connStr := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;database=%s;",
server, user, password, port, database)
var err error
db, err = sql.Open("sqlserver", connStr)
if err != nil {
log.Fataln(err.Error())
}
ctx := context.Background()
err = db.PingContext(ctx)
if err != nil {
log.Fataln(err.Error())
}
fmt.Println("Connected!")
log.Println(db == nil)
// 查詢一筆
one, err := getOne(103)
if err != nil {
log.Fatal(err.Error())
}
fmt.Println(one)
apps, err := getMany(103)
if err != nil {
log.Fatalln(err.Error())
}
fmt.Println(apps)
}
更新
- sql.DB 型別上用于更新(執行命令)的方法有:
- Exec
- ExecContext
services.go 檔案
func (a *app) Update() (err error) {
_, err = db.Exec("UPDATE dbo.App SET Name=@Name, [Order]=@Order WHERE Id=@Id",
sql.Named("Name", a.name), sql.Named("Order", a.order), sql.Named("Id", a.ID))
if err != nil {
log.Fatalln(err.Error())
}
return
}
main.go 檔案
a, _ := getOne(103)
fmt.Println(a)
a.name += " 1234"
a.order++
err = a.Update()
if err != nil {
log.Fatalln(err.Error())
}
a1, _ := getOne(103)
fmt.Println(a1)
洗掉
services.go 檔案
func (a *app) Delete() (err error) {
_, err = db.Exec("DELETE FROM dbo.App WHERE Id=@Id", sql.Named("Id", a.ID))
if err != nil {
log.Fatalln(err.Error())
}
return
}
其它
- Ping
- PingContext
- Prepare
- PrepareContext
- Transactions
- Begin
- Begin Tx
services.go 檔案
func (a *app) Insert() (err error) {
statement := `INSERT INTO dbo.App
(Name, NickName, Status, Level, [Order], Pinyin)
VALUES (@Name, 'Nick', &Status, @Level, @Order, '...');
SELE?T isNull(SCOPE_IDENTITY(), -1);`
stmt, err := db.Prepare(statement)
if err != nil {
log.Fatalln(err.Error())
}
defer stmt.Close()
err = stmt.QueryRow(
sql.Named("Name", a.name), sql.Named("Status", a.status),
sql.Named("Level", a.level),
sql.Named("Order", a.order)).Scan(&a.ID)
if err != nil {
log.Fatalln(err.Error())
}
return
}
main.go 檔案
a := app {
name: "Test",
order: 1123,
level: 10,
status: 1,
}
err = a.Insert()
if err != nil {
log.Fatalln(err.Error())
}
one, _ := getOne(a.ID)
fmt.Println(one)
三、MySQL CRUD 實踐
在MySQL中創建一個名為sql_test的資料庫
mysql> create database sql_test;
Query OK, 1 row affected (0.01 sec)
mysql> use sql_test;
Database changed
mysql> CREATE TABLE `user` (
-> `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
-> `name` VARCHAR(20) DEFAULT '',
-> `age` INT(11) DEFAULT '0',
-> PRIMARY KEY(`id`)
-> )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
mysql> show tables;
+--------------------+
| Tables_in_sql_test |
+--------------------+
| user |
+--------------------+
1 row in set (0.01 sec)
mysql>
main.go 檔案
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
// 定義一個全域物件db
var db *sql.DB
// 定義一個初始化資料庫的函式
func initDB() (err error) {
// DSN:Data Source Name
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 不會校驗賬號密碼是否正確
// 注意!!!這里不要使用:=,我們是給全域變數賦值,然后在main函式中使用全域變數db
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
// 嘗試與資料庫建立連接(校驗dsn是否正確)
err = db.Ping()
if err != nil {
return err
}
return nil
}
func main() {
err := initDB() // 呼叫輸出化資料庫的函式
if err != nil {
fmt.Printf("init db failed,err:%v\n", err)
return
}
fmt.Println("connect to database")
// one, err := queryRowDemo(1)
// if err != nil {
// log.Fatal(err.Error())
// }
// fmt.Println(one)
// u := user{
// name: "小喬1",
// age: 13,
// id: 0,
// }
// err = u.insertRowDemo()
// if err != nil {
// log.Fatalln(err.Error())
// }
// u, _ := queryRowDemo(1)
// fmt.Println("u:", u)
// u.name = "貂蟬"
// u.age = 16
// err = u.updateRowDemo()
// if err != nil {
// log.Fatalln(err.Error())
// }
// u1, _ := queryRowDemo(1)
// fmt.Println("u1:", u1)
last_row, _ := queryMultiRowDemo(0)
fmt.Println("last_row:", last_row)
// var u = new(user)
// u.id = 3
// err = u.deleteRowDemo()
// if err != nil {
// log.Fatalln(err.Error())
// }
}
services.go 檔案
package main
import (
"fmt"
)
// 查詢單條資料示例
func queryRowDemo(id int) (u user, err error) {
sqlStr := "select id, name, age from user where id=?"
// 非常重要:確保QueryRow之后呼叫Scan方法,否則持有的資料庫鏈接不會被釋放
err = db.QueryRow(sqlStr, id).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
return
}
// 查詢多條資料示例
func queryMultiRowDemo(id int) (u user, err error) {
sqlStr := "select id, name, age from user where id > ?"
rows, err := db.Query(sqlStr, id)
if err != nil {
fmt.Printf("query failed, err:%v\n", err)
return
}
// 非常重要:關閉rows釋放持有的資料庫鏈接
defer rows.Close()
// 回圈讀取結果集中的資料
for rows.Next() {
err = rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
return
}
// 插入資料
func (u *user) insertRowDemo() (err error) {
sqlStr := "insert into user(name, age) values (?,?)"
ret, err := db.Exec(sqlStr, u.name, u.age)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theID, err := ret.LastInsertId() // 新插入資料的id
if err != nil {
fmt.Printf("get lastinsert ID failed, err:%v\n", err)
return
}
fmt.Printf("insert success, the id is %d.\n", theID)
return
}
// 更新資料
func (u *user) updateRowDemo() (err error) {
sqlStr := "update user set age=? where id = ?"
ret, err := db.Exec(sqlStr, u.age, u.id)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected() // 操作影響的行數
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("update success, affected rows:%d\n", n)
return
}
// 洗掉資料
func (u *user) deleteRowDemo() (err error) {
sqlStr := "delete from user where id = ?"
ret, err := db.Exec(sqlStr, u.id)
if err != nil {
fmt.Printf("delete failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected() // 操作影響的行數
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("delete success, affected rows:%d\n", n)
return
}
models.go 檔案
package main
type user struct {
id int
age int
name string
}
運行
Code/go/go_sql_demo via ?? v1.20.3 via ?? base
? go build . && ./go_sql_demo
connect to database
id:1 name:小喬 age:16
id:2 name:小喬 age:12
last_row: {2 12 小喬}
Code/go/go_sql_demo via ?? v1.20.3 via ?? base took 3.3s
?
本文來自博客園,作者:QIAOPENGJUN,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17390319.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552195.html
標籤:其他
上一篇:openAI發布v0.2.0了
下一篇:返回列表
