[go]操作mysql(实战1)

226 阅读6分钟

0/参考网址

blog.csdn.net/weixin_5379…

juejin.cn/post/684490…

1/前言

go语言中自带的database/sql库包提供了保证sql或者类sql数据库的泛用接口,并没有提供具体的数据库驱动.
也就是说我们在使用database/sql库包的时候,必须注入至少一个数据库驱动。
也就是说,database/sql这个go内置的库包,必须和具有数据驱动功能的第三方库包一起使用。
我们常用的数据库基本上都有完整的第三方实现,例如“driver”可以理解为驱动
    github.com/go-sql-driver/mysql

2/下载依赖的库包

通过以下命令,安装依赖的库包
go get github.com/go-sql-driver/mysql

3/连接数据库,初始化客户端

下面这一段代码,就是要创建一个可以操作mysql数据库的客户端client,也可以理解为是一个具柄。
有了这个具柄,就可以去执行·增删改查·这些操作了
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"  // 导入 mysql 驱动 
)

// 声明客户端
// 相当于有了一个具柄,有了这个具柄,可以干很多事,就可以操作数据库了,很方便。

var mysql_client *sql.DB

func InitMysqlDB() (err error) { // 初始化mysql客户端
    // DSN:Data Source Name
    // dsn,字符串中,包含了连接mysql所需要的一些信息。
    // 比如用户名user,密码password,mysql的服务ip,port,还有数据库名称sql_test
    
    // 这段代码首先通过 sql.Open() 函数创建了一个 mysql 数据库连接。
    // 其中第一个参数是驱动名称,
    // 第二个参数是连接字符串,格式为 用户名:密码@协议(地址:端口)/数据库名称。 
    dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
    mysql_client, err = sql.Open("mysql", dsn)
   
    if err != nil { // 检测链接是否成功
        return err
    }
    
    // 尝试与数据库建立连接
    err = mysql_client.Ping()
    if err != nil {
        return err
    }
    
    // 如果都没问题,最后返回nil
    return nil
}

// 主函数
func main() {
    // 在主函数中,执行这个mysql的初始化函数,这样就后续使用mysql客户端了
    err := InitMysqlDB() 
    if err != nil {
       fmt.Printf("init db failed,err:%v\n", err)
       return
    }
}

4/CRUD(create,read,update,delete)

操作数据库

1/创建库,创建表

这个操作不是通过go代码来实现的,而是直接在mysql中,通过mysql的命令来实现的
我们先在MySQL中创建一个名为sql_test的数据库
    CREATE DATABASE sql_test;
    然后进入该数据库:
    use sql_test;

    执行以下命令创建一张用于测试的数据表user_table
    该表中有3个字段,id,name,age
    其中id这个字段是唯一键,是不能重复的。再往里写数据的时候,如果id重复,就会报错。
    CREATE TABLE `user_table` (
        `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;

2/查询数据

 查询数据,最后返回结果。
 为了查询方便,我们事先定义一个结构体,来存储查返回的数据
     type user struct {
            id   int
            name string
            age  int
 
     }

<1>单行查询

单行查询`db_client.QueryRow()`执行一次查询,并期望返回最多一行结果(即Row)。
QueryRow总是返回非nil的值,直到返回值的Scan方法被调用时,才会返回被延迟的错误。
具体的代码示例:
// 查询单条数据示例
func queryRowDemo() {
    // sql命令
    sql_command := "select id, name, age from user_table where id=?"
    
    // 初始化一个实例,结构体
    var u user
    
    // 确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
    err := mysql_client.QueryRow(sql_command, 1).Scan(&u.id, &u.name, &u.age)
    if err != nil {
       fmt.Printf("scan failed, err:%v\n", err)
       return
    }
    
    // 最后没有问题,打印日志
    log.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}

<2>多行查询-query

多行查询`mysql_client.Query()`执行一次查询,返回多行结果(即Rows),
一般用于执行select命令。参数args表示query中的占位参数。
具体代码示例:
// 查询多条数据示例
func queryMultiRowDemo() {
    sql_command := "select id, name, age from user where id > ?"
    rows, err := db.Query(sql_command, 0)
    if err != nil {
        fmt.Printf("query failed, err:%v\n", err)
        return
    }
    // 关闭rows释放持有的数据库链接
    defer rows.Close()

    // 循环读取结果集中的数据
    for rows.Next() {
        var u user  // 实例化一个结构体对象
        err := rows.Scan(&u.id, &u.name, &u.age)
        if err != nil {
            fmt.Println("scan failed, err:", err)
            return
        }
        fmt.Println(u.id, u.name, u.age)
    }
}

3/插入数据-insert

插入、更新和删除操作都使用`Exec`方法。
Exec执行一次命令(包括查询、删除、更新、插入等),返回的Result是对已执行的SQL命令的总结。
参数args表示query中的占位参数。
// 插入数据
func insertRowDemo() {
    sql_command := "insert into user(name, age) values (?,?)"
    ret, err := mysql_client.Exec(sql_command, "王五", 38)
    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)
}

4/更新数据-update

// 更新数据
func updateRowDemo() {
    sql_command := "update user set age=? where id = ?"
    ret, err := mysql_client.Exec(sql_command, 39, 3)
    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)
}

5/删除数据

// 删除数据
func deleteRowDemo() {
    sql_command := "delete from user where id = ?"
    ret, err := mysql_client.Exec(sql_command, 3)
    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)
}

事务

一个最小的不可再分的工作单元,就是事务。
通常一个事务,对应一个完成的业务,比如银行转账业务,该业务就是一个最小的工作单元。
这个完整的业务需要执行多次的DML(insertupdate,delete)语句共同联合完成。
在MySQL中只有使用了`Innodb`数据库引擎的数据库或表才支持事务。
事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。

mysql单条数据查询并返回结果(将 sql.Rows 转化为 map )

这里的sql.Rows就是查询单条数据的函数,然后返回的结果。

因为查询的是单条数据,所以返回值的类是map[string]string,及key-value的形式,
func GetResultRow(rows *sql.Rows) map[string]string{
    columns, _ := rows.Columns()    // 获取列
    scanArgs := make([]interface{}, len(columns))   // 填充数据
    values := make([]interface{}, len(columns))     // 存放数据值
    
    // 将values的指针存放到scanArgs里
    for i := range values {
        scanArgs[i] = &values[i]
    }
    
    // 用来装整个查询结果的map
    // 定义了一个record对象,是map类型,key和value都是string类型的
    record := make(map[string]string)
    
    for rows.Next() {
        // Scan方法赋值
        rows.Scan(scanArgs...)
        // 因为指针绑定,所以values里有值了,遍历赋值给record
        for i, v := range values {
            if v != nil {
                record[columns[i]] = string(v.([]byte)) // 需要进行断言,然后string方法转化为字符串
            }
        }
    }
    return record
}

查询多条数据,并返回结果(将 sql.Rows 转化为 map[int]map[string]string)

int是行号,因为返回的是多行数据,所以需要有行号。
这就是相当于在原来key-value的形式下,在外面又包装了一层key。
{1:{"id":"1","name":"zhangsan","age":"56"},
 2:{"id":"1","name":"lisi","age":"50"},
 3:{"id":"1","name":"wangwu","age":"70"},
 ......
 ......}
 
func GetResultRows(rows *sql.Rows) map[int]map[string]string {
    // 返回所有列
    columns, _ := rows.Columns()
    // 这里标识一行所有列的值,用 []byte 表示
    vals := make([][]byte, len(columns))
    // 这里标识一行填充数据
    scans := make([]interface{}, len(columns))
    // 这里scans 引用vals 把数据填充到 []byte 里
    for k, _ := range vals {
        scan[k] = &val[k]
    }
    i := 0
    result := make(map[int]map[string]string)
    for row.Next() {
        // 填充数据
        rows.scan(scans...)
        // 每行数据
        row := make(map[string]string)
        // 把vals中的数据赋值到row中
        for k, v := range vals {
            key := columns[k]
            // 这里把[]byte数据转化成string
            row[key] = string(v)
        }
        // 放入结果集
        result[i] = row
        i++
    }
    return result
}