Go連接MYSQL


Go原生提供了連接數據庫操作的支持,在用 Golang進行開發的時候,如果需要在和數據庫交互,則可以使用database/sql包。這是一個對關系型數據庫的通用抽象,它提供了標准的、輕量的、面向行的接口。

在Go中訪問數據庫需要用到sql.DB接口:它可以創建語句(statement)和事務(transaction),執行查詢,獲取結果。

使用數據庫時,除了database/sql包本身,還需要引入想使用的特定數據庫驅動。官方不提供實現,先下載第三方的實現,點擊這里查看各種各樣的實現版本。

本文測試數據庫為mysql,使用的驅動為:github.com/go-sql-driver/mysql,需要引入的包為:

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

解釋一下導入包名前面的"_"作用:

import 下划線(如:import _ github/demo)的作用:當導入一個包時,該包下的文件里所有init()函數都會被執行,然而,有些時候我們並不需要把整個包都導入進來,僅僅是是希望它執行init()函數而已。這個時候就可以使用 import _ 引用該包。

上面的mysql驅動中引入的就是mysql包中各個init()方法,你無法通過包名來調用包中的其他函數。導入時,驅動的初始化函數會調用sql.Register將自己注冊在database/sql包的全局變量sql.drivers中,以便以后通過sql.Open訪問。

執行數據庫操作之前我們准備一張表:

CREATE TABLE `user` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `name` varchar(45) DEFAULT '',
    `age` int(11) NOT NULL DEFAULT '0',
    `sex` tinyint(3) NOT NULL DEFAULT '0',
    `phone` varchar(45) NOT NULL DEFAULT '',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

1. 初始化數據庫連接:

DB, _ := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test")
//設置數據庫最大連接數
DB.SetConnMaxLifetime(100)
//設置上數據庫最大閑置連接數
DB.SetMaxIdleConns(10)
//驗證連接
if err := DB.Ping(); err != nil {
    fmt.Println("open database fail")
    return
}
fmt.Println("connnect success")

sql.Open()中的數據庫連接串格式為:"用戶名:密碼@tcp(IP:端口)/數據庫?charset=utf8"

DB的類型為:*sql.DB,有了DB之后我們就可以執行CRUD操作。Go將數據庫操作分為兩類:QueryExec。兩者的區別在於前者會返回結果,而后者不會。

  • Query表示查詢,它會從數據庫獲取查詢結果(一系列行,可能為空)。
  • Exec表示執行語句,它不會返回行。

此外還有兩種常見的數據庫操作模式:

  • QueryRow表示只返回一行的查詢,作為Query的一個常見特例。
  • Prepare表示准備一個需要多次使用的語句,供后續執行用。

2. 查詢操作

var user User
rows, e := DB.Query("select * from user where id in (1,2,3)")
if e == nil {
    errors.New("query incur error")
}
for rows.Next(){
    e := rows.Scan(user.sex, user.phone, user.name, user.id, user.age)
    if e != nil{
        fmt.Println(json.Marshal(user))
    }
}
rows.Close()
//單行查詢操作
DB.QueryRow("select * from user where id=1").Scan(user.age, user.id, user.name, user.phone, user.sex)

整體工作流程如下:

  1. 使用db.Query()來發送查詢到數據庫,獲取結果集Rows,並檢查錯誤。
  2. 使用rows.Next()作為循環條件,迭代讀取結果集。
  3. 使用rows.Scan從結果集中獲取一行結果。
  4. 使用rows.Err()在退出迭代后檢查錯誤。
  5. 使用rows.Close()關閉結果集,釋放連接。

3. 增刪改和Exec

通常不會約束你查詢必須用Query,只是Query會返回結果集,而Exec不會返回。所以如果你執行的是增刪改操作一般用Exec會好一些。Exec返回的結果是ResultResult接口允許獲取執行結果的元數據:

type Result interface {
    // 用於返回自增ID,並不是所有的關系型數據庫都有這個功能。
    LastInsertId() (int64, error)
    // 返回受影響的行數。
    RowsAffected() (int64, error)
}

4. 准備查詢

如果你現在想使用占位符的功能,where 的條件想以參數的形式傳入,Go提供了db.Prepare語句來幫你綁定。准備查詢的結果是一個准備好的語句(prepared statement),語句中可以包含執行時所需參數的占位符(即綁定值)。准備查詢比拼字符串的方式好很多,它可以轉義參數,避免SQL注入。同時,准備查詢對於一些數據庫也省去了解析和生成執行計划的開銷,有利於性能。

占位符

PostgreSQL使用$N作為占位符,N是一個從1開始遞增的整數,代表參數的位置,方便參數的重復使用。MySQL使用?作為占位符,SQLite兩種占位符都可以,而Oracle則使用:param1的形式。

MySQL               PostgreSQL            Oracle
=====               ==========            ======
WHERE col = ?       WHERE col = $1        WHERE col = :col
VALUES(?, ?, ?)     VALUES($1, $2, $3)    VALUES(:val1, :val2, :val3)
stmt, e := DB.Prepare("select * from user where id=?")
query, e := stmt.Query(1)
query.Scan()

5. 事務的使用

通過db.Begin()來開啟一個事務,Begin方法會返回一個事務對象Tx。在結果變量Tx上調用Commit()或者Rollback()方法會提交或回滾變更,並關閉事務。在底層,Tx會從連接池中獲得一個連接並在事務過程中保持對它的獨占。事務對象Tx上的方法與數據庫對象sql.DB的方法一一對應,例如Query,Exec等。事務對象也可以准備(prepare)查詢,由事務創建的准備語句會顯式綁定到創建它的事務。

//開啟事務
tx, err := DB.Begin()
if err != nil {
    fmt.Println("tx fail")
}
//准備sql語句
stmt, err := tx.Prepare("DELETE FROM user WHERE id = ?")
if err != nil {
    fmt.Println("Prepare fail")
    return false
}
//設置參數以及執行sql語句
res, err := stmt.Exec(user.id)
if err != nil {
    fmt.Println("Exec fail")
    return false
}
//提交事務
tx.Commit()

我們來一個完整的sql操作:

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "github.com/pkg/errors"
    "strings"
)

//數據庫配置
const (
    userName = "root"
    password = "123456"
    ip       = "127.0.0.1"
    port     = "3306"
    dbName   = "test"
)

//Db數據庫連接池
var DB *sql.DB

type User struct {
    id    int64
    name  string
    age   int8
    sex   int8
    phone string
}

//注意方法名大寫,就是public
func InitDB() {
    //構建連接:"用戶名:密碼@tcp(IP:端口)/數據庫?charset=utf8"
    path := strings.Join([]string{userName, ":", password, "@tcp(", ip, ":", port, ")/", dbName, "?charset=utf8"}, "")
    //打開數據庫,前者是驅動名,所以要導入: _ "github.com/go-sql-driver/mysql"
    DB, _ = sql.Open("mysql", path)
    //設置數據庫最大連接數
    DB.SetConnMaxLifetime(100)
    //設置上數據庫最大閑置連接數
    DB.SetMaxIdleConns(10)
    //驗證連接
    if err := DB.Ping(); err != nil {
        fmt.Println("open database fail")
        return
    }
    fmt.Println("connnect success")
}

//查詢操作
func Query() {
    var user User
    rows, e := DB.Query("select * from user where id in (1,2,3)")
    if e == nil {
        errors.New("query incur error")
    }
    for rows.Next() {
        e := rows.Scan(user.sex, user.phone, user.name, user.id, user.age)
        if e != nil {
            fmt.Println(json.Marshal(user))
        }
    }
    rows.Close()
    DB.QueryRow("select * from user where id=1").Scan(user.age, user.id, user.name, user.phone, user.sex)

    stmt, e := DB.Prepare("select * from user where id=?")
    query, e := stmt.Query(1)
    query.Scan()
}

func DeleteUser(user User) bool {
    //開啟事務
    tx, err := DB.Begin()
    if err != nil {
        fmt.Println("tx fail")
    }
    //准備sql語句
    stmt, err := tx.Prepare("DELETE FROM user WHERE id = ?")
    if err != nil {
        fmt.Println("Prepare fail")
        return false
    }
    //設置參數以及執行sql語句
    res, err := stmt.Exec(user.id)
    if err != nil {
        fmt.Println("Exec fail")
        return false
    }
    //提交事務
    tx.Commit()
    //獲得上一個insert的id
    fmt.Println(res.LastInsertId())
    return true
}

func InsertUser(user User) bool {
    //開啟事務
    tx, err := DB.Begin()
    if err != nil {
        fmt.Println("tx fail")
        return false
    }
    //准備sql語句
    stmt, err := tx.Prepare("INSERT INTO user (`name`, `phone`) VALUES (?, ?)")
    if err != nil {
        fmt.Println("Prepare fail")
        return false
    }
    //將參數傳遞到sql語句中並且執行
    res, err := stmt.Exec(user.name, user.phone)
    if err != nil {
        fmt.Println("Exec fail")
        return false
    }
    //將事務提交
    tx.Commit()
    //獲得上一個插入自增的id
    fmt.Println(res.LastInsertId())
    return true
}

func main() {
    InitDB()
    Query()
    defer DB.Close()
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM