这是我参与「第五届青训营 」笔记创作活动的第 3 天 。
一、主要内容:
这篇笔记记录了Gorm框架的基本用法
go get gorm.io/driver/mysql
go get gorm.io/gorm
二、详细知识点:
1.模型的概念
1)模型映射
数据库里面的表映射到Go语言中的结构,这个结构就叫做模型。 例如mysql里面的表:
CREATE TABLE goods(
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id,商品id',
name varchar(30) NOT NULL COMMENT '商品名',
price decimal(10,2) unsigned NOT NULL COMMENT '商品价格',
type_id int(10) unsigned NOT NULL COMMENT '商品类型id',
createtime int(10) NOT NULL DEFAULT 0 COMMENT '创建时间',
PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
翻译为模型后如下:
type Good struct{
Id int `gorm:"AUTO_INCREMENT"`
Name string `gorm:"type:varchar(25)"`//可以不用
Price float64
TypeId int `gorm:"size:10"`
CreateTime int64 `gorm:"size:10; column:createtime"`
}
- 默认gorm对struct字段名使用SnakeCase命名风格,需要全部转化成小写字母,并且单词之间使用下划线分割;
- 默认情况下使用ID作为主键;
- 使用结构体SnakeCase风格的附属形式作为表名;
- 使用CreateAt、UpdateAt字段追踪创建、更新时间
2)标签定义
标签定义可以对模型中的字段进行翻译时的解释。 常用标签: column 指定列名 primaryKey 指定主键
- 忽略字段
- 其他查看官方文档:gorm.io/zh_CN/docs/…
3)表名映射
如果不想用默认的表名格式,可以自行使用下列代码定义
- 静态表名
func (结构名) TableName() string {
return "你想定义的表名"
}
- 动态表名(临时表名)
db.Table("临时表名")
4)结构体嵌套
需要在标签处加上"embedded"表示是嵌套结构体(Q:那么继承需要吗?)
good Good `gorm:"embedded"`
2.数据库连接
1)dsn参数
gorm库使用dsn作为连接数据库的参数,格式为:
//username 数据库账号
//password 数据库密码
//host 数据库连接地址,IP或者域名
//port 数据库端口
//Dbname 数据库名
username:password@tcp(host:port)/Dbname?charset=utf8mb4&parseTime=True&loc=Local&timeout=10
//填上参数后的例子
//username = root
//password = 123456
//host = localhost
//port = 3306
//Dbname = gorm
//后面K/V值对参数含义为:
// charset=utf8mb4
// parseTime=True 支持把数据库datatime和date类型转换为golang的time.Time类型
// loc=local 使用系统本地时区
// timeout=10 设置10秒后连接超时
// readTimeout - 读超时时间,0代表不限制
// writeTimeout - 写超时时间,0代表不限制
//username 数据库账号
//password 数据库密码
//host 数据库连接地址,IP或者域名
//port 数据库端口
//Dbname 数据库名
username:password@tcp(host:port)/Dbname?charset=utf8mb4&parseTime=True&loc=Local&timeout=10
//填上参数后的例子
//username = root
//password = 123456
//host = localhost
//port = 3306
//Dbname = gorm
//后面K/V值对参数含义为:
// charset=utf8mb4
// parseTime=True 支持把数据库datatime和date类型转换为golang的time.Time类型
// loc=local 使用系统本地时区
// timeout=10 设置10秒后连接超时
// readTimeout - 读超时时间,0代表不限制
// writeTimeout - 写超时时间,0代表不限制
2)连接数据库
dsn := root:123456@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local&timeout=10
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
- 调试模式:
db.Debug() - 连接池配置
db.DB().SetMaxOpenConns(100) //设置数据库连接池最大连接数
db.DB().SetMaxIdleConns(20) //设置数据库连接池最大空闲连接数连接数,
//sql任务需要执行的连接数大于20的连接会被连接池关闭
3.数据的增删改查
1)插入数据
①普通插入
初始化模型类型的一个变量后调用
db.Create(&user)
//调用函数后
user.ID //自动加上的主键
result.Error //error
result.RowsAffected //插入记录的条数
②指定字段创建
db.Select("username","password").Create(&user)
③忽略字段
db.Omit("username").Create(&user)
④批量插入
var users = []User{{Username: "zhangsan"},{Username: "lisi"},{Username: "zhaoliu"}}
db.Create(&users)
或者
db.CreateInBatches(&users,2) //一批次2条
⑤使用map创建
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "lisi", "Age": 18},
{"Name": "zhangsan", "Age": 20},
})
需要注意的是map插入并不会自动填充主键(Q:那么自己也不填充主键会报错吗?)
⑥sql表达式
一般用于需要使用sql中的加密方式
db.Model(&User{}).Create(map[string]interface{}){
"username": "liso",
"password": clause.Expr{SQL: "md5(?)", Vars: []interface{}{"123456"}},
}
⑦使用原生sql语句
db.Exec("inser into users (username,password,createtime) values (?,?,?)", user.Username, user.Password, user.CreateTime)
可以在值对应的地方使用sql加密函数,如在user.Password处写 "md5('123456')"
2)更新数据
①Save
Save函数会将所有字段都更新一次,效率较低
var good Good
db.Where("id=?",3).Take(&good)
good.Price = 5.2
db.Save(&good) //此时的Save是更新
//或者
good := {
//...
//主键id没有出现过
}
db.Save(&good) //此时的Save是插入
②Update
var good Good
db.Where("id=?",3).Take(&good)
db.Model(&good).Update("price",5.2) //Update(列名,值)
或者
db.Model(&Good{}).Where("id=?",3).Update("price",5.2) //Update(列名,值)
//更新多列为
Updates(Good{
price: 5.2,
name: "栗子",
})
③指定字段更新
依然是Select()和Omit()配合使用即可
④表达式更新
用Update函数,第二个参数为gorm.Expr("表达式")
其中表达式可以出现列名
db.Model(&good).Update("price",gorm.Expr("price+1")) //Update(列名,值)
3)删除数据
①主键删除
db.Delete(&Good{}, 1)
②查询删除
db.Where(...).Delete(&Good{})
4)查询数据
①查询行
- Find() 查找多个,一般使用切片接受,没有拿出东西不会报错!
- Limit() 限制拿出多少条数据
- Take() 等效于 First() ,都是拿出第一个; 如果没有拿出东西会报错
Not Found - Last() 拿出最后一个
②查询列
var names []string
db.Model(&Good{}).Pluck("要查询的列名",&names)
③条件查询
- Where(query,args) query中可以为in,not in等各种sql语句中使用的查询条件,遇到参数用 ? 占位,在第二个参数处填充。
- Select() 设置select子句,返回指定的字段;Select()中可以使用聚合函数,如:
var total int
db.Model(&Good{}).Select("count(*) as total").Pluck("total",&total)
④Order排序和分页分组
分页
var goods []Good
db.Order("id desc").Limit(10).Offset(10).Find(&goods)
上述示例为:按照id降序,每页10条数据,取第十页的数据
分组
- Group("分组的字段名")
- Having("条件")
- 返回结果可以用Find()函数,这就要求传入Find的参数结构必须与表模型一致;
- 可以使用Scan(),只要参数结构的字段与前面Select()拿出来的字段完全一致即可
- Group()函数必须搭配Select函数一起使用
复杂查询
使用sql语句
sql := "sql语句"
db.Raw(sql,语句中占位的参数).Scan(&result)
⑤Count()
Count()返回匹配查询的行数
db.Model(Good{}).Count(&total)
5)Session会话
本内容老师在讲课时没有重点提及,所以这里就不研究了,放一个网站以后需要时再参考。 Gorm Session——迹忆客 (jiyik.com)
6)事务
①自动事务
err := db.Transaction(func(tx \*gorm.DB) error{
//进行db操作,但是要用tx
//如果出现错误,就return err,会自动回滚
//return nil就会提交
})
- 自动事务的嵌套事务就是在func里面再次调用一次Transaction()
②手动事务
tx := db.Begin()
//执行操作...
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
③保存点
tx.SavePoint("保存点名称")
//...执行各种操作
tx.Rollbackto("保存点")
tx.Commit()
7)Hook
- Hook是在创建、查询、更新、删除等操作之前、之后调用的函数;
- 如果任何钩子返回错误,gorm将停止后续操作并回滚事务
函数签名为
func(调用的表模型)(tx *gorm.DB) error执行的顺序为
// 开始事务
BeforeSave
BeforeCreate/BeforeUpdate
// 操作,保存
AfterCreate/AfterUpdate/AfterFind(查询到多少条就出发多少次)
AfterSave
// 提交或回滚事务
8)gorm性能提高
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipfaultTransaction: true,
PrepareStmt: true
})
- 关闭默认事务
- 缓存预编译
9)gorm扩展
- GORM代码生成工具 github.com/go-gorm/gen
- GORM分片库方案 github.com/go-gorm/sha…
- GORM手动索引 github.com/go-gorm/hin…
- GORM乐观锁 github.com/go-gorm/opt…
- GORM读写分离 github.com/go-gorm/dbr…
- GORM OpenTelemetry github.com/go-gorm/ope…
其他
快速开始
在main文件里面添加如下代码再go mod tidy就可以一键导入gorm框架
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main(){
dsn := "root:123456@tcp(localhost:3306)/mybase?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("连接失败", err)
return
} else {
fmt.Println("连接成功")
}
}
先初始化数据库
在这里补充一个在代码中新建表的方法,之前都是在数据库里面已有的表操作的,比较麻烦。
先贴上官方文档:迁移 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
常用的Migrator方法
- 创建表(User是在go语言中定义的结构体)
m:=db.Migrator()
m.CreateTable(&User{})
- 是否存在表
//以结构体的方式查
m.HasTable(&User{})
//以表名的方式查
fmt.Println(m.HasTable("gva_users"))
- 删除表
err = m.DropTable(&User{})
if err != nil {
fmt.Println(err)
}
err = m.DropTable("gva_users")
if err != nil {
fmt.Println(err)
}
if m.HasTable(&User{}) {
m.DropTable(&User{})
} else {
m.CreateTable(&User{})
}