golang sqlx用法笔记

11 篇文章 0 订阅

golang sqlx用法笔记
原创westhod 发布于2018-07-26 15:05:58 阅读数 5674  收藏
展开

在上一篇文章中吐槽了golang 数据库查询接口(https://mp.csdn.net/postedit/80799266),后来在网上找到了sqlx这个第三方库,用起来确实爽多了,这里记录下学习和用法的心得

安装:
使用命令即可

go get github.com/jmoiron/sqlx
介绍:
官方的介绍如下:

sqlx is a library which provides a set of extensions on go's standard database/sql library. 
The sqlx versions of sql.DB, sql.TX, sql.Stmt, et al. all leave the underlying interfaces untouched, 
so that their interfaces are a superset on the standard ones. 
This makes it relatively painless to integrate existing codebases using database/sql with sqlx.
 
Major additional concepts are:
 
Marshal rows into structs (with embedded struct support), maps, and slices
Named parameter support including prepared statements
Get and Select to go quickly from query to struct/slice
In addition to the godoc API documentation, there is also some standard documentation that explains 
how to use database/sql along with sqlx.
大意就是sqlx是golang 标准database/sql的扩展,使用sqlx的接口跟原先的接口方法没什么两样,但有如下扩展:

1.可将行记录映射如struct(内嵌struct也支持),map与slices          <--这正是我之前想要的效果

2.支持在preprared statement 中使用命名参数

3.Get 和Select的查询结果到struct/slice更快速

sqlx也增加了许多接口,方便开发者使用,后面会讲到。

使用:
还是上篇文章的案例,不过,这次插入数据交给go代码来实现,便于说明sqlx方法的使用,go代码如下

package main
 
import (
    "database/sql"
    _"github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    "log"
    "fmt"
)
 
type Student struct {
    Id         int    `db:"id"`
    Name       string `db:"name"`
    Nick       string `db:"nick"`
    Country    string `db:"country"`
    Province   string `db:"province"`
    City       string `db:"city"`
    ImgUrl     string `db:"img_url"`
    Status     int    `db:"status"`
    CreateTime string `db:"create_time"`
}
 
func main()  {
    dns := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", dbuser, dbpwd, dbhost, dbname)
    db, err := sqlx.Connect("mysql", dns)
    if err != nil {
        log.Fatalln(err)
    }
    defer db.Close()
 
    tx := db.MustBegin()
    tx.MustExec(`INSERT INTO student VALUES ('1', 'Jack', 'Jack', 'England', '', '', 'http://img2.imgtn.bdimg.com/it/u=3588772980,2454248748&fm=27&gp=0.jpg', '1', '2018-06-26 17:08:35');`)
    tx.MustExec(`INSERT INTO student VALUES ('2', 'Emily', 'Emily', 'England', '', '', 'http://img2.imgtn.bdimg.com/it/u=3588772980,2454248748&fm=27&gp=0.jpg', '2', null);`)
    err = tx.Commit()
    if err != nil {
        log.Fatalln(err)
    }
 
}
生成数据表的代码如下:

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `nick` varchar(64) DEFAULT NULL,
  `country` varchar(128) DEFAULT NULL,
  `province` varchar(64) DEFAULT NULL,
  `city` varchar(64) DEFAULT NULL,
  `img_url` varchar(256) DEFAULT NULL,
  `status` int(11) DEFAULT NULL,
  `create_time` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
sqlx.Connect("mysql", dns)内部实现了Open()和Ping(),因此调用Connect()方法后,则可直接使用该对象进行数据库操作,这里开启一个事务,插入2条数据,代码运行非常成功。假如把插入的第二条语句修改为:

tx.MustExec(`INSERT INTO student VALUES ('2', null, 'Emily', 'England', '', '', 'http://img2.imgtn.bdimg.com/it/u=3588772980,2454248748&fm=27&gp=0.jpg', '2', null);`)
运行程序报

panic: Error 1048: Column 'name' cannot be null
再看数据表中没有插入数据,表明事务没有被提交。这些方法用起来感觉还是好多了!这里的方法名中的Must应当是multi-statement的缩写,而不是英文must(必须)的意思。

tx.MustExec()函数也可以带参数,跟其他语言的语法很像,比如:

tx.MustExec(`INSERT INTO student (id, name, nick, country, province, city, img_url, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?);`, "3", "Jobs", "Jobs", "America", "", "", "http://img2.imgtn.bdimg.com/it/u=3588772980,2454248748&fm=27&gp=0.jpg", "3")
也可使用命名参数,然后直接传一个struct对象进去:

    s := Student {
        Id : 4,
        Name : "Cook", 
        Nick : "Cook",
        Country : "America",
        Province : "", 
        City : "",
        ImgUrl : "http://img2.imgtn.bdimg.com/it/u=3588772980,2454248748&fm=27&gp=0.jpg",
        Status : 1,
    }
 
    _, err = tx.NamedExec(`INSERT INTO student VALUES (:id, :name, :nick, :country, :province, :city, :img_url, :status, :create_time);`, s)
    if err != nil {
        log.Fatalln(err)
    }
这里的命名参数需要跟struct的tag对应,假如把字段Name的tag修改为:`db:"username"`,运行时报错:

ould not find name name in main.Student{Id:4, Name:"Cook", Nick:"Cook", Country:"America", Province:"", City:"", ImgUrl:"http://img2.imgtn.bdimg.com/it/u=3588772980,2454248748&fm=27&gp=0.jpg", Status:1, CreateTime:sql.NullString{String:"", Valid:false}}
现在,我要查询数据了,按条件查询一条记录:

    var s Student
    err = db.Get(&s, "SELECT * FROM student WHERE name=?", "Cook")
    if err != nil {
        log.Fatalln(err)
    }
 
    fmt.Println(s)
程序运行正常,这样使用实在太方便不过了,现在我要查询多条记录:

    var stds [] Student
    db.Select(&stds, "SELECT * FROM student")
 
    for i, v := range stds {
        fmt.Printf("%d--->%v\n", i+1, v)
    }
发现只打印了一条记录,怎么回事?加上错误打印看看:

    var stds [] Student
    err = db.Select(&stds, "SELECT * FROM student")
    if err != nil {
        log.Fatalln(err)
    }
 
    for i, v := range stds {
        fmt.Printf("%d--->%v\n", i+1, v)
    }
运行时报:

sql: Scan error on column index 8: unsupported Scan, storing driver.Value type <nil> into type *string
大意是,在Scan index为8即第9个字段时出错(索引从0开始计算),不支持的Scan,数据库字段为nil转为 *string时出错。仔细想想,在插入记录时,只有第一条记录的create_time字段是有值的,其他都是默认为NULL,数据库也是设计为可空的,再查看官方使用示例时,也提到:

if you have null fields and use SELECT *, you must use sql.Null* in your struct
即如果你有null字段且使用SELECT *,你的结构体定义中,字段需使用类型sql.Null*,create_time是字符串,所以需定义为sql.NullString,另外还有其他类型:sql.NullBool、sql.NullFloat64、sql.NullInt64。赶紧屁颠屁颠把Student的struct修改如下:

type Student struct {
    Id         int    `db:"id"`
    Name       string `db:"name"`
    Nick       sql.NullString `db:"nick"`
    Country    sql.NullString `db:"country"`
    Province   sql.NullString `db:"province"`
    City       sql.NullString `db:"city"`
    ImgUrl     sql.NullString `db:"img_url"`
    Status     sql.NullInt64  `db:"status"`
    CreateTime sql.NullString `db:"create_time"`
}
由于name字段在设计时,是不可空的,所以这里仍然定义为string类型。

现在程序能正常运行,打印4条记录了。

也可以使用Queryx把行数据查询出来,然后再Scan到struct中:

    rows, err := db.Queryx("SELECT * FROM student")
    if err != nil {
        fmt.Println(err)
        panic(err)
    }
 
    for rows.Next() {
        var stu Student
        err := rows.StructScan(&stu)
        if err != nil {
            log.Fatalln(err)
        }
 
        fmt.Println(stu)
    }
相对来说,还是Select简洁得多了。
————————————————
版权声明:本文为CSDN博主「westhod」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/westhod/article/details/81205758

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
个人学习golang笔记,从各种教程中总结而来,作为入门参考。目录如下 目录 1. 入门 1 1.1. Hello world 1 1.2. 命令行参数 2 2. 程序结构 3 2.1. 类型 4 2.1.1. 命名类型(named type)与未命名类型(unamed type) 4 2.1.2. 基础类型(underlying type) 4 2.1.3. 可赋值性 5 2.1.4. 类型方法集 6 2.1.5. 类型声明 6 2.2. 变量 8 2.2.1. 变量声明 8 2.2.2. 类型零值 12 2.2.3. 指针 13 2.3. 赋值 17 2.4. 包和文件 17 2.5. 作用域 18 2.6. 语句 19 2.7. 比较运算符 20 2.8. 类型转换 21 2.9. 控制流 23 2.9.1. If 23 2.9.2. Goto 24 2.9.3. For 25 2.9.4. Switch 25 2.9.5. break语句 31 2.9.6. Continue语句 31 3. 基础数据类型 31 3.1. golang类型 31 3.2. Numeric types 32 3.3. 字符串 33 3.3.1. 什么是字符串 33 3.3.2. 字符串底层概念 35 3.3.3. 获取每个字节 38 3.3.4. Rune 39 3.3.5. 字符串的 for range 循环 40 3.3.6. 用字节切片构造字符串 41 3.3.7. 用rune切片构造字符串 42 3.3.8. 字符串的长度 42 3.3.9. 字符串是不可变的 42 3.3.10. UTF8(go圣经) 43 3.4. 常量 45 3.4.1. 常量定义 45 3.4.2. 常量类型 46 3.4.3. Iota 46 4. 组合数据类型 47 4.1. 数组 47 4.1.1. 数组概述 47 4.1.2. 数组的声明 49 4.1.3. 数组的长度 50 4.1.4. 遍历数组 50 4.1.5. 多维数组 51 4.2. 切片 52 4.2.1. 什么是切片 52 4.2.2. 切片概述 55 4.2.3. 创建一个切片 55 4.2.4. 切片遍历 57 4.2.5. 切片的修改 58 4.2.6. 切片的长度和容量 60 4.2.7. 追加切片元素 62 4.2.8. 切片的函数传递 65 4.2.9. 多维切片 66 4.2.10. 内存优化 67 4.2.11. nil slice和empty slice 69 4.2.12. For range 70 4.3. 结构 71 4.3.1. 什么是结构体? 71 4.3.2. 结构体声明 73 4.3.3. 结构体初始化 77 4.3.4. 嵌套结构体(Nested Structs) 81 4.3.5. 匿名字段 82 4.3.6. 导出结构体和字段 84 4.3.7. 结构体相等性(Structs Equality) 85 4.4. 指针类型 86 4.5. 函数 87 4.6. map 87 4.6.1. 什么是map 87 4.6.2. 声明、初始化和make 89 4.6.3. 给 map 添加元素 91 4.6.4. 获取 map 中的元素 91 4.6.5. 删除 map 中的元素 92 4.6.6. 获取 map 的长度 92 4.6.7. Map 的相等性 92 4.6.8. map的排序 92 4.7. 接口 93 4.7.1. 什么是接口? 93 4.7.2. 接口的声明与实现 96 4.7.3. 接口的实际用途 97 4.7.4. 接口的内部表示 99 4.7.5. 空接口 102 4.7.6. 类型断言 105 4.7.7. 类型选择(Type Switch) 109 4.7.8. 实现接口:指针接受者与值接受者 112 4.7.9. 实现多个接口 114 4.7.10. 接口的嵌套 116 4.7.11. 接口的零值 119 4.8. Channel 120 4.9. 类型转换 120 5. 函数 120 5.1. 函数的声明 121 5.2. 一个递归函数的例子( recursive functions) 121 5.3. 多返回值 121 5.4. 命名返回值 121 5.5. 可变函数参数 122 5.6. Defer 123 5.6.1. Defer语句介绍 123 5.6.2. Defer使用场景 128 5.7. 什么是头等(第一类)函数? 130 5.8. 匿名函数 130 5.9. 用户自定义的函数类型 132 5.10. 高阶函数(装饰器?) 133 5.10.1. 把函数作为参数,传递给其它函数 134 5.10.2. 在其它函数中返回函数 134 5.11. 闭包 135 5.12. 头等函数的实际用途 137 6. 微服务创建 140 6.1. 使用net/http创建简单的web server 140 6.2. 读写JSON 144 6.2.1. Marshal go结构到JSON 144 6.2.2. Unmarshalling JSON 到Go结构 146 7. 方法 146 7.1. 什么是方法? 146 7.2. 方法示例 146 7.3. 函数和方法区别 148 7.4. 指针接收器与值接收器 153 7.5. 那么什么时候使用指针接收器,什么时候使用值接收器? 155 7.6. 匿名字段的方法 156 7.7. 在方法中使用值接收器 与 在函数中使用值参数 157 7.8. 在方法中使用指针接收器 与 在函数中使用指针参数 159 7.9. 在非结构体上的方法 161 8. 并发入门 162 8.1. 并发是什么? 162 8.2. 并行是什么? 162 8.3. 从技术上看并发和并行 163 8.4. Go 对并发的支持 164 9. Go 协程 164 9.1. Go 协程是什么? 164 9.2. Go 协程相比于线程的优势 164 9.3. 如何启动一个 Go 协程? 165 9.4. 启动多个 Go 协程 167 10. 信道channel 169 10.1. 什么是信道? 169 10.2. 信道的声明 169 10.3. 通过信道进行发送和接收 169 10.4. 发送与接收默认是阻塞的 170 10.5. 信道的代码示例 170 10.6. 信道的另一个示例 173 10.7. 死锁 174 10.8. 单向信道 175 10.9. 关闭信道和使用 for range 遍历信道 176 11. 缓冲信道和工作池(Buffered Channels and Worker Pools) 179 11.1. 什么是缓冲信道? 179 11.2. 死锁 182 11.3. 长度 vs 容量 183 11.4. WaitGroup 184 11.5. 工作池的实现 186 12. Select 188 12.1. 什么是 select? 188 12.2. 示例 189 12.3. select 的应用 190 12.4. 默认情况 190 12.5. 死锁与默认情况 191 12.6. 随机选取 191 12.7. 这下我懂了:空 select 191 13. 文件读写 191 13.1. GoLang几种读文件方式的比较 197 14. 个人 197 14.1. ++,-- 198 14.2. 逗号 198 14.3. 未使用的变量 199 14.4. Effective go 199 14.4.1. 指针 vs. 值 199 14.5. 可寻址性-map和slice的区别 201 14.5.1. slice 201 14.5.2. map 202 14.6. golang库 203 14.6.1. unicode/utf8包 203 14.6.2. time包 205 14.6.3. Strings包 205 14.6.4. 输入输出 212 14.6.5. 正则处理 224 14.6.6. Golang内建函数 226

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值