mysql

使用标准库

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql" // _ 开头import的含义是匿名导入,会执行导入包的 init 初始化函数。这句代码的功能即是加载了mysql驱动,驱动加载好后,database/sq标准库才能正常使用。
)

var db *sql.DB

func initMySQL() (err error) {
    // DSN:Data Source Name
    dsn := "root:root1234@tcp(127.0.0.1:13306)/sql_demo"
    // 去初始化全局的db对象而不是新声明一个db变量
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        panic(err)
    }
    // 做完错误检查之后,确保db不为nil
    // 尝试与数据库建立连接(校验dsn是否正确)
    err = db.Ping()
    if err != nil {
        fmt.Printf("connect to db failed, err:%v\n", err)
        return
    }
  // database/sql 包已经维护了连接池,可供多个goroutine并发使用
    // 数值需要业务具体情况来确定
    //db.SetConnMaxLifetime(time.Second*10) 连接最长存活时间
    db.SetMaxOpenConns(100) // 最大连接数
    db.SetMaxIdleConns(10)  // 最大空闲连接数
    return
}

type user struct {
    id   int
    age  int
    name string
}

// 预处理查询示例
func prepareQueryDemo() {
    sqlStr := "select id, name, age from user where id > ?"
    stmt, err := db.Prepare(sqlStr)
    if err != nil {
        fmt.Printf("prepare failed, err:%v\n", err)
        return
    }
    defer stmt.Close()
  // 预处理适用场景:批量的执行同一条SQL语句,只是查询的字段值不同时,提前让服务器编译,一次编译多次执行,节省后续编译的成本,如下,当还需要查询stmt.Query(1)、stmt.Query(2) …… 时,增删改查都适用。
    rows, err := stmt.Query(0)
    if err != nil {
        fmt.Printf("query failed, err:%v\n", err)
        return
    }
    defer rows.Close()
    // 循环读取结果集中的数据
    for rows.Next() {
        var u user
        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)
    }
}

// 任何时候都不应该自己拼接SQL语句,不能相信用户输入的数据一定是安全的。
// 不可以通过fmt包(或其他方式)直接拼接SQL,通过database/sql包的QueryRow、Prepare、Exec来传参是可以的
// sql注入示例
func sqlInjectDemo(name string) {
    sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
    fmt.Printf("SQL:%s\n", sqlStr)
    var u user
    err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
    if err != nil {
        fmt.Printf("exec failed, err:%v\n", err)
        return
    }
    fmt.Printf("user:%#v\n", u)
}

// 事务操作示例
func transactionDemo() {
    tx, err := db.Begin() // 开启事务
    if err != nil {
        if tx != nil {
            tx.Rollback() // 回滚
        }
        fmt.Printf("begin trans failed, err:%v\n", err)
        return
    }
    sqlStr1 := "Update user set age=30 where id=?"
    ret1, err := tx.Exec(sqlStr1, 2)
    if err != nil {
        tx.Rollback() // 回滚
        fmt.Printf("exec sql1 failed, err:%v\n", err)
        return
    }
    affRow1, err := ret1.RowsAffected()
    if err != nil {
        tx.Rollback() // 回滚
        fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
        return
    }

    sqlStr2 := "Update user set age=40 where id=?"
    ret2, err := tx.Exec(sqlStr2, 3)
    if err != nil {
        tx.Rollback() // 回滚
        fmt.Printf("exec sql2 failed, err:%v\n", err)
        return
    }
    affRow2, err := ret2.RowsAffected()
    if err != nil {
        tx.Rollback() // 回滚
        fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
        return
    }

    // 当affRow1 == 1 && affRow2 == 1
    fmt.Println(affRow1, affRow2)
    if affRow1 == 1 && affRow2 == 1 {
        fmt.Println("事务提交啦...")
        tx.Commit() // 提交事务
    } else {
        tx.Rollback()
        fmt.Println("事务回滚啦...")
    }

    fmt.Println("exec trans success!")
}

func main() {
    if err := initMySQL(); err != nil {
        fmt.Printf("connect to db failed, err:%v\n", err)
    }
    // Close() 用来释放掉数据库连接相关的资源
    defer db.Close() // 注意这行代码要写在上面err判断的下面
    fmt.Println("connect to db success")

    //prepareQueryDemo()
    //prepareInsertDemo()
  //SQL注入示例
  //sqlInjectDemo("xxx ' or 1=1#")
    //select id, name, age from user where name='xxx ' or 1=1#'   
  //#在SQL中代表注释,注释掉了后面的'符号,1=1恒成立,所以以上SQL语句等价于 select id, name, age from user
    //sqlInjectDemo("xxx' union select * from user #")
    //sqlInjectDemo("xxx' and (select count(*) from user) <10 #")
    transactionDemo()
    fmt.Println("查询结束了...")
}

database/sql 定义了一套接口,规定了要实现的功能,在具体的驱动中再来实现这些接口功能,如以上的mysql,这样设计,在调用 database/sql 的接口时对于不同的驱动就可以复用同一套代码(其他驱动分别实现这些接口功能即可,如PostgreSQL、Microsoft SQL Server等),这个设计充分体现了 golang 面相接口开发的特点。

第三方包sqlx

type User struct {
  Id   int        `db:"id"`
    Name string `db:"name"`
    Age  int    `db:"age"`
}
// 查询单条数据示例
func queryRowDemo() {
    sqlStr := "select id, name, age from user where id=?"
    var u user
    err := db.Get(&u, sqlStr, 1)
    if err != nil {
        fmt.Printf("get failed, err:%v\n", err)
        return
    }
    fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age)
}
// 查询多条数据示例
func queryMultiRowDemo() {
    sqlStr := "select id, name, age from user where id > ?"
    var users []user
    err := db.Select(&users, sqlStr, 0)
    if err != nil {
        fmt.Printf("query failed, err:%v\n", err)
        return
    }
    fmt.Printf("users:%#v\n", users)
}
// QueryByIDs 根据给定ID查询
func QueryByIDs(ids []int)(users []User, err error){
    // 动态填充id
    query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?)", ids)
    if err != nil {
        return
    }
    // sqlx.In 返回带 `?` bindvar的查询语句, 使用Rebind()重新绑定它
    query = DB.Rebind(query)
    err = DB.Select(&users, query, args...)
    return
}
// 使用NamedExec实现批量插入,不需要拼接占位符?和数据了,方便很多
func BatchInsertUsers(users []*User) error {
    _, err := DB.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users)
    return err
}

Last updated

Was this helpful?