1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
数据持久化是必不可少的一部分,平日里开发,如果是专注于业务开发,那 99% 的工作也就是CRUD(增删改查)工程师了。
废话不多说,说了也没用,直接上手来操作,对数据库进行访问。
对数据库操作,少不了各个语言对数据库操作的驱动,就像 JAVA 中有 mysql-driver 的驱动包,拉取下来就可以通过JDBC 对数据库操作了,当然 Spring、Mybatis 等框架也提供了对数据库很方便的操作。
那在 Go 中也是提供了驱动 github.com/go-sql-driver/mysql
,我们通过 go get 拉取驱动来进行CRUD操作。
使用命令:go get github.com/go-sql-driver/mysql
来拉取驱动库。
首先在 MYSQL 数据库创建一个测试库叫 go_demo
,然后来添加一张 User
表来操作,脚本如下:
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`age` int(0) NULL DEFAULT NULL,
`create_time` date NOT NULL,
`update_time` date NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `users` (name, address, age, create_time, update_time) VALUES ('悟空', '五指山下', 18, NOW(), NOW());
INSERT INTO `users` (name, address, age, create_time, update_time) VALUES ('唐僧', '大唐东土', 21, NOW(), NOW());
INSERT INTO `users` (name, address, age, create_time, update_time) VALUES ('八戒', '高老庄', 19, NOW(), NOW());
INSERT INTO `users` (name, address, age, create_time, update_time) VALUES ('沙森', '流沙河', 25, NOW(), NOW());
select * from users;
下面的示例中,首先使用
database/sql
和github.com/go-sql-driver/mysql
包来连接 MySQL 数据库,获取到一个连接示例 db, 再通过db.Exec()
函数来执行insert
语句,成功把 白龙马 指派到了师徒四人的队伍中。
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 连接 MYSQL 数据库
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_demo")
if err != nil {
log.Fatal(err)
}
// defer 关键字来延迟关闭连接
defer db.Close()
// 插入数据
insertSql := "INSERT INTO users (name, address, age, create_time, update_time) VALUES (?, ?, ?, ?, ?)"
datetime := time.Now()
_, insertErr := db.Exec(insertSql, "白龙马", "东海", 18, datetime, datetime)
if insertErr != nil {
log.Fatal(insertErr)
}
fmt.Println("插入数据成功!")
}
下面的示例中,首先使用
database/sql
和github.com/go-sql-driver/mysql
包来连接 MySQL 数据库,获取到一个连接示例 db, 再通过db.Exec()
函数来执行delete
语句,成功把 悟空 逐出了队伍。
可以看出来,从获取数据库连接到执行 SQL 语句,Go 代码基本一样的,想必定有框架做了此事了……
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 连接mysql数据库
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_demo")
if err != nil {
log.Fatal(err)
}
defer db.Close()
//删除悟空
deleteSql := "delete from users where id = ?"
_, deleteErr := db.Exec(deleteSql, 1)
if deleteErr != nil {
log.Fatal(deleteErr)
}
fmt.Println("删除成功!")
}
更新操作其实无外乎也是获取数据库连接,执行
update
语句,我们可以先封装一个公共的函数来获取数据库连接,在执行操作。
这里的包是
common
, 在另一个包中,并且 Conn 首字母大写,表明外部包是可以调用的。
package common
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
func Conn() *sql.DB {
// 连接mysql数据库
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_demo")
if err != nil {
log.Fatal(err)
}
return db
}
下面案例,从上面封装的 common 包中通过 Conn() 函数获取数据库连接,再进行操作。
**注意:**这里需要用common.Conn()
获取连接,不能用*common.Conn()
获取,如果用*common.Conn()
获取则在 后续的判断中db != nil
出错:mismatched types sql.DB and untyped nil
,因为db
是一个指针类型,不能直接与nil
进行比较。
package main
import (
"fmt"
"log"
"gotest.com/test/src/common"
)
func main() {
// 获取数据库连接
db := common.Conn()
if db != nil {
defer db.Close()
// 更新八戒的地址,不再是高老庄
updateSql := "update users set address = ? where id = ?"
_, updateErr := db.Exec(updateSql, "天上人间", 3)
if updateErr != nil {
log.Fatal(updateErr)
}
fmt.Println("更新成功!")
} else {
fmt.Println("获取数据库连接失败!")
}
}
在下面的案例中:
- 通过 自己封装的
common.Conn()
获取一个数据库链接- 利用
defer
关键词来延迟关闭链接- 通过
db.Query()
来执行一个查询- 循环遍历,
rows.Next()
来判断是否有下一条记录,如果返回 true,大括号{}
表示一个代码块,其中包含了每次迭代式要执行的代码。- 在
{}
中,可以对每条记录进行操作,比如利用Scan()
来扫描将记录的值赋值给相应定义的变量。- 主要注意的是,rows.Scan(&id, &name…) 是指向 id、name 变量的指针,才能把记录中的值赋值给当前的变量。
package main
import (
"fmt"
"log"
"gotest.com/test/src/common"
)
func main() {
// 获取数据库连接
db := common.Conn()
selectSql := "select id,name,address,age from users"
rows, err := db.Query(selectSql)
if err != nil {
log.Fatal(err)
}
// 延迟关闭数据库连接
defer db.Close()
// 遍历结果, rows是数据库查询结果的迭代器,Next()函数来判断是否下一条记录
for rows.Next() {
var id int
var name string
var address string
var age int
err := rows.Scan(&id, &name, &address, &age)
if err != nil {
log.Fatal(err)
}
fmt.Println("id:", id, "name:", name, "address:", address, "age:", age)
}
}
下面示例中,使用
db.Begin()
来开启事务,本来要更新唐僧的地址为女儿国,唐僧也对女儿国王动心了,本来和女儿国王都说好了,玉帝哥哥陪国王白头到老(没头发如何白头到老),但是贫僧有要事在身,不得不离开,所以即便更新成功了,也得通过tx.Rollback()
回滚回去。
package main
import (
"fmt"
"log"
"gotest.com/test/src/common"
)
func main() {
// 获取数据库连接
db := common.Conn()
if db != nil {
defer db.Close()
// 开启事务
tx, beginErr := db.Begin()
if beginErr != nil {
log.Fatal(beginErr)
}
// 更新唐僧的地址,不再是东土大唐,留在了女儿国
// 注意,这里要用事务 tx.Exec() 来执行操作
updateSql := "update users set address = ? where id = ?"
result, updateErr := db.Exec(updateSql, "女儿国", 2)
count, _ := result.RowsAffected()
// 如果影响行数大于 0
if count > 0 {
// 唐僧有使命在身,怎能为了儿女私情误了朕的大事
tx.Rollback()
fmt.Println("唐僧最终离开了女儿国!")
return
}
if updateErr != nil {
log.Fatal(updateErr)
// 更新操作发生异常,事务回滚
tx.Rollback()
}
// 提交事务
commitErr := tx.Commit()
if commitErr != nil {
log.Fatal(commitErr)
fmt.Println("更新提交失败!")
}
fmt.Println("更新成功!")
} else {
fmt.Println("获取数据库连接失败!")
}
}
可以看到,更新条数是 1,说明更新成功了!!!
PS D:\workspaceGo\src\database> go run .\updateTest.go
更新条数: 1
唐僧最终离开了女儿国!
2023/08/19 21:29:26 sql: transaction has already been committed or rolled back
exit status 1
注意
:在使用 tx, beginErr := db.Begin()
开启事务后,需要用 tx.Exec()
来执行更新操作,这样后续的tx.Rollback()
和tx.Commit()
操作才会生效。刚开始我就使用了db.Exec()
来执行的更新操作,结果回滚就不生效,才发现犯了这等低级错误…
此篇只对 Go 语言操作数据库进行简单的 CRUD 操作的示例,就像 JAVA 中用 JDBC 查库那样的基础操作,可发现基本就是通过获取的数据库链接通过函数进行对SQL的执行操作。
那 Go 操作 MySQL 有哪些优势呢?
database/sql
对SQL数据库直接访问的接口,允许直接操作数据库。有哪些缺点呢?
第三方开源库用于操作 MySQL 数据库:
database/sql
包,支持多种数据库的操作。它提供了通用的接口的方法,让你能够进行基本的数据库操作。