这是我参与「第三届青训营 -后端场」笔记创作活动的的5篇笔记。
课程导学链接
【Go 语言原理与实践学习资料(下)】第三届字节跳动青训营 - 后端专场 - 掘金 (juejin.cn)
课程PPT链接
设计模式之 database sql与 GORM实践.pdf
这一节课程主要是讲解Golang中database/sql以及GORM框架的应用以及底层实现的一些原理,讲师张金柱老师就是GORM框架的作者,技术力很强,课程中带着大家讲解了许多框架设计以及具体库的原理,鼓励大家多看源代码,争取成为开源项目甚至Golang的贡献者。
不过对于零基础的同学(包括我,在此之前我甚至不知道数据库与SQL到底是什么)来说,可能理解这节课相关内容会比较困难,但是数据库相关的内容对于课程项目来说又十分重要,所以本篇笔记不会太过于在意其库以及框架的实现原理,重点放在对于库和框架的使用上。
课程大纲如下图所示(本篇笔记只涉及一二部分)
database/sql
在了解database/sql之前我们需要知道什么是数据库和SQL,可以参考廖雪峰的SQL教程进行一个快速阅读进行扫盲(已经掌握的同学可以不用管这部分),只需要简单了解即可,因为后面使用GORM之后我们不需要会编写SQL就可以完成对数据库的访问。
快速了解之后我们会知道对关系型数据库进行访问需要使用SQL语句,但是现在需要在Go语言程序中使用数据库,这就是database/sql发挥用处的地方了。
以下内容来自课堂讲解以及database/sql的官方教程,其中包含了一定的个人理解,做了很多取舍,只保留了最基础的主干内容,从一个数据库小白的视角讲解了database/sql的使用。
database/sql是Go语言提供的一个用来访问面向行的数据库的包,提供了非常轻量级的通用接口来访问各种不同的底层关系型数据库。
导入database/sql与驱动包
database/sql是一个通用的接口,其与底层的数据库不是绑定的,你可以轻松地切换你的底层数据库而无需更改你的Go程序。这同时意味着database/sql中不可能包含了针对每一种数据库的驱动,具体需要使用某个数据库时,你还需要使用相应的驱动的包。
这里我们以MySQL driver举例
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
注意我们将driver导入为匿名包,代码中我们是不能直接使用其中的内容的,但是其中的init()函数会被执行,init()函数内容如下,可以看到其中将mysql的driver注册到了database/sql中。
func init() {
sql.Register("mysql", &MySQLDriver{})
}
访问数据库
database/sql中最重要的接口就是sql.DB,要注意sql.DB不是某一个数据库的映射,更加不是数据库连接,它只是一个数据库接口的一个抽象,在底层为你做两件事情
- 通过driver为你开启和关闭到底层数据库的连接
- 为你管理一个连接池
这一层抽象的目的是为了让你无需关心对于底层数据库的并发访问,你所要做的就是使用它来创建你的连接,然后在使用完成后将连接释放。
既然这么好用,让我们看看如何创建一个sql.DB吧,代码非常简单
func main() {
db, err := sql.Open("mysql",
"user:password@tcp(127.0.0.1:3306)/hello")
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
这里Open()的第一个参数就是我们前面注册的名字"mysql",第二个参数则是用来告诉driver如何访问底层数据库(对于每种数据库,其格式不尽相同,具体查询driver的文档即可),db的类型为*sql.DB,后面的错误检查和defer db.Close()可以看作一种惯用法,每次都如此使用即可。
注意这里的Open()有些违反直觉,其并没有创建任何连接,第一个真正的连接直到被使用时才创建,如果你想要立马检查你对数据库连接是否可用,可以使用Ping()函数
err = db.Ping()
if err != nil {
// do something here
}
最后一点关于sql.DB的提醒就是,最佳编程范式是仅程序中仅创建和关闭一次,将其视为一个长期存活的对象,不要反复调用Open()和Close()。
查询操作
database/sql中的函数名有特殊的约定,函数名包含Query的函数只用于查询,返回若干个行,不返回行的操作应该用Exec()。
让我们直接看一个查询的例子
var (
id int
name string
)
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&id, &name)
if err != nil {
log.Fatal(err)
}
log.Println(id, name)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
对于这个例子,官方教程给出了许多提示,大多都是关于如何正确关闭rows以及错误处理,总结起来其实就是老老实实按照例子来做,基本就不会有问题。
只有一点值得额外注意,如果你循环执行了多次查询,不要在循环中使用defer rows.Close(),因为defer到函数退出时才会执行,这样会占用很多系统资源,应该在循环尾部显式调用rows.Close()。
很多时候我们会改动查询的某一个参数进行多次查询,database/sql贴心地为我们准备了Prepare()方法
stmt, err := db.Prepare("select id, name from users where id = ?")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
rows, err := stmt.Query(1)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
// ...
}
if err = rows.Err(); err != nil {
log.Fatal(err)
}
其中的?是MySQL中的占位符,后面调用查询时可以为其给出不同的参数。
上面的用法都非常标准,但是对于一个很小的查询来说却显得有些笨重,例如至多返回一行的情况,database/sql也考虑到了这点,为我们提供了相应的简单方法,可以直接像下面这样使用
var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(name)
修改操作
前面已经说到了,不涉及返回行的操作,例如INSERT,UPDATE,DELETE等都应该使用Exec()方法,下面是一个例子
stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
if err != nil {
log.Fatal(err)
}
res, err := stmt.Exec("Dolly")
if err != nil {
log.Fatal(err)
}
lastId, err := res.LastInsertId()
if err != nil {
log.Fatal(err)
}
rowCnt, err := res.RowsAffected()
if err != nil {
log.Fatal(err)
}
log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)
以上就是对database/sql的一个急速教程,到这里你就已经可以使用database/sql的API来进行数据库的访问了,更多的内容注入事务、连接池的知识等可以参看官方教程以及文档。
GORM入门使用
我其实在一开始看到GORM是完全懵的,搜索了解到其是ORM框架,那什么又是ORM框架呢?为什么我们有了前面的database/sql已经能完成数据库的访问和修改等操作还需要ORM框架呢?
相信上面这些问题也是很多同学疑惑的点,我查到阮一峰的ORM实例教程中第一小节讲的比较清晰,大家可以查阅参考。
GORM是Go语言中一个开发者友好的ORM框架,有着完善的中文文档,一切用法都可以查阅官方文档。
官方文档虽然权威,但是其中并没有一个清晰的逻辑脉络,初学者看了还是云里雾里。推荐阅读这个GORM入门教程,不过要注意现在的import的地址已经有所不同,具体参考GORM文档-连接到数据库,另外文中对相应部分都给出了官方文档的链接,非常适合收藏查阅。
有了GORM我们就不需要去编写SQL语句,使用GORM提供的方法就可以完成各种数据库交互,GORM结合一些网络框架就可以搭建我们的服务端程序啦(Golang: gorm with MySQL and gin., Build a simple REST API using Go, MySQL, Gorm, and mux)。
写在最后
本文仅仅从一个完全小白的角度讲解了database/sql的基本用法和GORM的基本用法,这对于实际项目来说肯定是远远不够的,但是整个脉络都是很清晰的,文中的教程等都是精心挑选的高质量教程,希望能给同为小白的同学们一些帮助,不至于像我一样听完课程感觉一头雾水,想要自学但又不知从何下手。(持续更新不易,希望大家看了文章觉得有帮助可以点个赞鼓励一下作者)
一口吃不成个胖子,课程中GORM的底层原理和最佳实践等内容可以等初步使用之后在进行学习,学习是一个循序渐进的过程,把基础的内容掌握好,后面学习起来也会轻松很多。
另外本节课的课后习题对我来说难度过大,放弃了。