连接数据库是典型的CS编程,服务器端被动等待客户端建立TCP连接,并在此连接上进行特定的应用层 协议。但一般用户并不需要了解这些细节,这些都被打包到了驱动库当中,只需要简单的调用打开就可以指定协议连接到指定的数据库。

数据库的种类和产品太多,协议太多,Go官方很难提供针对不同数据库的驱动程序,往往由各数据库官 方或第三方给出不同开发语言的驱动库。但是,为了Go语言可以提前定义操作一个数据库的所有行为 (接口)和数据(结构体)的规范,这些定义在database/sql下。

MySQL驱动

  • https://github.com/go-sql-driver/mysql 支持 database/sql,使用广泛
  • https://github.com/ziutek/mymysql 支持 database/sql,支持自定义接口
  • https://github.com/Philio/GoMySQL 不支持 database/sql,支持自定义接口

注册驱动:

// github.com/go-sql-driver/mysql/mysql/driver.go 代码中有注册驱动
func init() { // 83行
 sql.Register("mysql", &MySQLDriver{})
}

连接

DSN例子 https://github.com/go-sql-driver/mysql#examples

 使用示例:

package logg

import (
    "io"

    "github.com/rs/zerolog"
)

func InfoLog(out io.Writer) zerolog.Logger {
    zerolog.TimeFieldFormat = "2006-01-02 15:04:05.000 +0800"
    logger := zerolog.New(out).With().Timestamp().Logger().Level(1)
    return logger
}
logg/logg.go
package main

import (
    "cabel/logg"
    "database/sql" // 接口定义库
    "fmt"
    "os"
    "time"

    _ "github.com/go-sql-driver/mysql" // 具体实现,可以不调用,加载一下就行,在init中注册mysql这个名字
    "github.com/rs/zerolog/log"
)

var db0 *sql.DB

func init() {
    connstr := "gopher:123456@tcp(172.0.0.1:3306)/gopher"
    var err error
    // 接口库的方法,指定使用的数据库种类,名称就要找到该名称对应的数据库的驱动
    db0, err = sql.Open("mysql", connstr)
    if err != nil {
        log.Err(err).Send()
    }
    db0.SetConnMaxIdleTime(time.Second * 30) // 连接超时时间
    // 数据库连接最大生命周期,用于指定数据库连接在被重用之前的最大存活时间
    db0.SetConnMaxLifetime(time.Second * 60)
    db0.SetMaxOpenConns(100) // 最大连接数,默认0为不限制
    db0.SetMaxIdleConns(10)  // 空闲连接数
}

// 定义结构体,结构体中字段与数据库中字段顺序最好一一对应
type Emp struct {
    emp_no                            int
    birth_date, first_name, last_name string
    gender                            int
    hire_date                         string
}

func main() {
    f0, err := os.OpenFile("logs/info.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        log.Err(err).Send()
    }
    defer f0.Close()
    f1, err := os.OpenFile("logs/error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        log.Err(err).Caller().Send()
    }
    defer f1.Close()
    l0 := logg.InfoLog(f0)
    l1 := logg.InfoLog(f1)
    err = db0.Ping()
    if err == nil {
        l0.Info().Msg("数据库初始化成功")
    } else {
        l1.Err(err).Msg("数据库连接失败")
    }

    // 单行查询
    row := db0.QueryRow("select * from employees where emp_no > ? limit 1", 10007)
    if row.Err() != nil {
        l1.Err(row.Err()).Caller().Msg("数据库查询失败")
    }
    var r Emp
    row.Scan(&r.emp_no, &r.birth_date, &r.first_name,
        &r.last_name, &r.gender, &r.hire_date)
    fmt.Println(r)

    // 预编译查询,多行查询
    stmt, err := db0.Prepare("select * from employees where emp_no > ? and emp_no < ? order by emp_no limit 3")
    if err != nil {
        l1.Err(err).Caller().Msg("预编译查询语句失败")
    }
    rows, err := stmt.Query("10003", 10008)
    if err != nil {
        l1.Err(err).Caller().Msg("查询失败")
    }
    // Next来遍历,每一次,rows都指向当前行
    for rows.Next() {
        err = rows.Scan(&r.emp_no, &r.birth_date, &r.first_name,
            &r.last_name, &r.gender, &r.hire_date)
        if err != nil {
            l1.Err(err).Msg("填充失败")
        }
        fmt.Println(r)
        t0, err := time.Parse("2006-01-02", r.birth_date) // 时间解析
        if err != nil {
            l1.Err(err).Send()
        }
        fmt.Printf("%T %[1]v\n", t0)
    }
}
main.go

返回结果:

使用db.Prepare预编译并使用参数化查询

  • 对预编译的SQL语句进行缓存,省去了每次解析优化该SQL语句的过程
  • 防止注入攻击
  • 使用返回的sql.Stmt操作数据库
posted on 2023-07-23 13:02  自然洒脱  阅读(96)  评论(0编辑  收藏  举报