【Golang】快速复习指南QuickReview(十一)——数据库访问(MySql为例)
对于业务层面的开发,就离不开数据库的访问。
1.创建项目
俗话说卖钱不卖钱,摊摊儿要扯圆,甭管怎样,我们先建立一个标准的golang
项目,来访问数据库。
1.1 go mod 管理依赖
go mod init gitee.com/RandyField/sqltest
1.2 安装mysql驱动包
go get -u github.com/go-sql-driver/mysql
1.3 创建文件
cd sqltest
New-Item main.go
New-Item service.go #数据库访问方法
New-Item models.go #数据映射结构
2.连接数据库
main.go
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
const (
user = "root"
password = "111111"
database = "push-center"
protocol = "tcp"
address = "127.0.0.1"
charset = "utf8mb4"
parseTime = "True"
)
var db *sql.DB
func main() {
/*
https://pkg.go.dev/github.com/go-sql-driver/mysql
https://github.com/denisenkom/go-mssqldb
gorm-sqlx
*/
// Connstr := fmt.Sprintf("%s:%s@%s(%s)/%s?charset=%s&parseTime=%s",
// user, password, protocol, address, database, charset, parseTime)
// fmt.Println(Connstr)
// Connstr := "root:111111@tcp(127.0.0.1)/push-center?charset=utf8mb4&parseTime=True"
//parseTime 是查询结果是否自动解析为时间
//loc 是MySQL的时区设置
Connstr := "root:111111@tcp(127.0.0.1)/push-center?charset=utf8mb4&parseTime=True&loc=Local"
fmt.Println("start...")
var err error
db, err = sql.Open("mysql", Connstr)
if err != nil {
log.Fatalln(err.Error())
}
ctx := context.Background()
//验证连接是否有效
err = db.PingContext(ctx)
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println("Connected")
}
import "database/sql"
Golang
的database/sql
包提供了保证SQL或类SQL数据库的泛用接口。我们的数据库操作(编码)也只在database/sql
包上进行。
import _ "github.com/go-sql-driver/mysql"
- 连接数据库,需要加载目标数据库的驱动,
Golang
是没有提供官方的数据库驱动,所有的数据库驱动都是第三方驱动,但是它们都遵循sql.driver
包里面定义的接口 - 我们并不直接使用这个驱动,所以使用
_
引入,只需要在引入驱动包时调用包内init
函数进行自动注册。
- 连接数据库,需要加载目标数据库的驱动,
实际上,需要使用
sql
包的Register()
数据库驱动名称
并实现
driver.Driver()
接口的struct
注册:
sql.Register("mysql",&drv{})
-
Connstr
:username/password@tcp(ipaddress)/database?parm1=&parm2=
- 连接字符串,尤其注意后面的参数,博主在这里使用
.net
的EFCore
插入时间,值总是正确,而在使用golang
时却总是有问题(晚8小时),无论在代码层面做何种转换。parseTime
是查询结果是否自动解析为时间loc
是MySQL的时区设置
- 连接字符串,尤其注意后面的参数,博主在这里使用
-
sql.Open()
:仅仅是配置连接,但并不真正连接,需要两个参数:- 数据库驱动名称
- 数据库连接字符串
- 返回一个执行
sql.DB
这个struct的指针:*sql.DB
这个指针才是我们操作数据库的关键钥匙,我们编码通过这个指针发送
sql
命令,获得结果。它抽象了底层数据库连接池并对其维护,且并发安全,这便意味着我们可以在多个goroutine
中并发使用。针对*sql.DB
有两种用法:
- 定义全局变量,然后到处使用
- 定义变量,将其作为参数传递给函数或者方法
-
ctx := context.Background()
:Context
(上下文)类型可以携带截止时间、取消信号和其他请求范围的值,并且可以横跨API边界和进程。但是这里的context
包的Background()
返回的Context很特殊,非nil
的空Context
,不会被取消也没有值,没有截止时间。- 通常用在main函数、初始化或测试中,作为传入请求的顶级
Context
- 通常用在main函数、初始化或测试中,作为传入请求的顶级
-
db.PingContext(ctx)
:验证与数据库的连接是否仍然有效,如有必要则建立一个连接。
3.访问数据库
访问之前我们需要能够映射数据库表的struct
,但是struct
非必需条件。
models.go
package main
//Notifypush 推送通知
type notifypush struct {
Id int `json:"Id"`
AppName string `json:"APP"`
Target int `json:"Platform"`
TargetValue *string
PushType int
DeviceType int `json:"Device"`
Title *string
Body *string `json:"Content"`
CreateTime string
}
如果数据库字段有Null,可空类型, 结构体或者变量,都需要定义指针类型,否则会发生运行时错误。
3.1 查询单条
service.go
package main
import (
"log"
"time"
)
// GetById 根据ID获取单条数据
func GetById(id int) (notifypush, error) {
n := notifypush{}
err := db.QueryRow("select Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notifypush where Id=?",
id).Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
// err := db.QueryRow("select Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notitypush where Id=@Id",
// sql.Named("Id", id)).Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
return n, err
}
如果是
sqlserver
,参数是使用@+参数名来进行站位,并配合sql.Named()
函数使用。mysql
不能这样,否则会报错mysql: driver does not support the use of Named Parameters
3.2 查询多条
// GetMultiRow 获取多条数据
func GetMultiRow(id int) (ns []notifypush, err error) {
rows, err := db.Query("SELECT Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notifypush WHERE Id>?",
id)
// 非常重要:关闭rows释放持有的数据库链接
defer rows.Close()
for rows.Next() {
n := notifypush{}
err = rows.Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
if err != nil {
log.Fatalln(err.Error())
}
ns = append(ns, n)
}
return
}
3.2 修改、插入、删除
修改、插入、删除都是cmd,在sql
包中只有一个方法:Exec
,这里就省略delete
操作,实际业务上很少使用物理删除。
Update
Update操作,需要定义方法(结构体为接收者)
//Update 更新
func (push *notifypush) Update() error {
_, err := db.Exec("UPDATE notifypush SET AppName=?,Target=?,PushType=?,DeviceType=? WHERE Id=?",
push.AppName, push.Target, push.PushType, push.DeviceType, push.Id)
return err
}
Insert
//Insert 插入
func Insert() error {
// _, err := db.Exec("INSERT INTO notifypush(AppName,Target,PushType,DeviceType,CreateTime) VALUES(?,?,?,?,?)",
// "test-insert-app", 1, 2, 3, time.Now())
// 改用 prepare
sql := `INSERT INTO notifypush(AppName,Target,PushType,DeviceType,CreateTime) VALUES(?,?,?,?,?)`
stmt, err := db.Prepare(sql)
if err != nil {
log.Fatalln(err.Error())
}
defer stmt.Close()
_, err = stmt.Exec("insert-app", 1, 2, 3, time.Now())
return err
}
4.调用方法
4.1 编码
main.go
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
const (
user = "root"
password = "111111"
database = "push-center"
protocol = "tcp"
address = "127.0.0.1"
charset = "utf8mb4"
parseTime = "True"
)
var db *sql.DB
func main() {
//ommit connect code
//查询
notify, err := GetById(10)
if err != nil {
log.Fatalf(err.Error())
}
data, err := json.Marshal(notify)
fmt.Printf("推送消息为:%s\n", data)
//更新
notify.AppName = "smart"
err = notify.Update()
if err != nil {
log.Fatalf(err.Error())
}
notify, err = GetById(10)
if err != nil {
log.Fatalf(err.Error())
}
data, err = json.Marshal(notify)
fmt.Printf("更新后:%s\n", data)
//插入
err = Insert()
if err != nil {
log.Fatalf(err.Error())
}
//查询多条
ns, err := GetMultiRow(24)
datas, err := json.Marshal(ns)
fmt.Printf("推送消息为:%s\n", datas)
}
4.2 运行
这里我们的项目是一个具有多个文件.go
的module
,所以不能简单使用go run main.go
。需要先编译,才能运行
go build #编译会生成sqltest.exe
.\sqltest.exe #运行
5.ORM
5.1 GORM
GORM是GoLang
中最出色的ORM
框架,支持MySQL
、PostgreSQL
、Sqlite
、SQL Server
,功能非常强大,也可以直接执行SQL并获取结果集。还有数据库迁移。博主把他看作Golang
版本的EntityFramework
。
5.2 Sqlx
Sqlx是对GoLang
标准database/sql
的扩展。其特点是:
- 把SQL执行的结果集转化成数据结构(
Struct
、Maps
、Slices
)。 - 支持问号(?)或命名的
Prepared Statements
,避免SQL注入的安全问题
在博主看来,这个更像是一个golang
版本的dapper
。
参考连接
- 原文作者:Garfield
- 原文链接:http://www.randyfield.cn/post/2020-12-21-golang-mysql/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。