1 基本使用
1.1 连接数据库
func BasicUsage() {
//定义DSN
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
//连接服务器(池)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
//open成功
fmt.Println(db)
}
1.2 基于模型迁移表结构
- 自动根据结构体在对应数据库生成表结构
// 创建模型
type Article struct {
//嵌入基础模型
gorm.Model
//定义字段
Subject string
Likes uint
Published bool
PublishTime time.Time
AuthorID uint
}
func BasicUsage() {
//定义DSN
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
//连接服务器(池)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
//open成功
//fmt.Println(db)
//基于模型完成表结构(设计)的迁移(定于)
if err := db.AutoMigrate(&Article{}); err != nil {
log.Fatal(err)
}
}
1.3 基本的crud
var DB *gorm.DB
func init() { //初始化操作
//定义DSN
const dsn = "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
//连接服务器(池)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
//open成功
//fmt.Println(db)
//基于模型完成表结构(设计)的迁移(定于)
if err := db.AutoMigrate(&Article{}); err != nil {
log.Fatal(err)
}
DB = db
}
// 增
func Create() {
//构建Article类型数据
article := &Article{
Subject: "gorm 增操作",
Likes: 0,
Published: true,
PublishTime: time.Time{},
AuthorID: 2,
}
//DB.Create 完成数据库的insert
if err := DB.Create(article).Error; err != nil {
log.Fatal(err)
}
//print
fmt.Println(article)
}
// 查
// Find()多个 First()单个
func Retrieve(id uint) {
//初始化Article模型,零值
article := &Article{}
//DB.First()
if err := DB.First(article, id).Error; err != nil {
log.Fatal(err)
}
//print
fmt.Println(article)
}
//更新
/*
1.先确定更新的对象
2.设置对象属性字段
3.将对象存储
*/
func Update() {
//获取需要更新的对象
article := &Article{}
if err := DB.First(article, 1).Error; err != nil {
log.Fatal(err)
}
//更新对象字段
article.AuthorID = 23
article.Likes = 512
article.Subject = "新的文章标题"
//存储 DB.save()
if err := DB.Save(article).Error; err != nil {
log.Fatal(err)
}
}
// 删除
func Delete() {
//获取模型对象
article := &Article{}
if err := DB.First(article, 2).Error; err != nil {
log.Fatal(err)
}
//DB.delete()
if err := DB.Delete(article).Error; err != nil {
log.Fatal(err)
}
}
1.4 debug 日志
// debug
func Debug() {
//insert
article := &Article{
Subject: "这是一条新插入的数据",
AuthorID: 50,
}
if err := DB.Create(article).Error; err != nil {
log.Fatal(err)
}
//select
if err := DB.Debug().First(article, article.ID).Error; err != nil {
log.Fatal(err)
}
}
// 自定义日志
var logWriter io.Writer
logWriter, _ = os.OpenFile("./sql.log", os.O_CREATE|os.O_APPEND, 0644) //因为全局都要用 所以不写 logWriter.Close()
customLogger := logger.New(log.New(logWriter, "\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond, //慢查询阈值
LogLevel: logger.Info,
IgnoreRecordNotFoundError: false, //保持记录不存在的错误
//不彩色化
Colorful: false,
})
//连接服务器(池)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//设置为自定义日志
Logger: customLogger,
})
2 模型定义
2.1 表名定义
//连接服务器(池)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//设置为自定义日志
Logger: customLogger,
//设置默认的命名策略的选项
NamingStrategy: schema.NamingStrategy{
TablePrefix: "msb_",
SingularTable: true,
NameReplacer: nil,
NoLowerCase: false,
},
})
if err != nil {
log.Fatal(err)
}
- 生成表名为 my_box
func Migrate() {
if err := DB.AutoMigrate(&Post{}, &Category{}, &PostCategory{}, &Box{}); err != nil {
log.Fatal(err)
}
}
2.2 字段类型映射
2.3 指针类型与非指针类型区别
- 若表中字段不为null,则使用T类型就够了 因为不存在例如(数据库中为null,对应到结构体中值为0,则0到底是不是有用的值)
2.4 自定义字段类型
type CustomTypeModel struct {
gorm.Model
FTime time.Time
FNullTime sql.NullTime
Fstring string
FNullString sql.NullString
FUUID uuid.UUID
FNullUUID uuid.NullUUID
}
func CustomType() {
//初始化模型
ctm := &CustomTypeModel{}
//迁移数据表
DB.AutoMigrate(ctm)
//创建
ctm.FTime = time.Time{} //零值
ctm.FNullTime = sql.NullTime{} //零值
ctm.Fstring = "" //零值
ctm.FNullString = sql.NullString{} //零值
ctm.FUUID = uuid.New()
ctm.FNullUUID = uuid.NullUUID{}
DB.Create(ctm)
//查询
DB.First(ctm, ctm.ID)
//判断字段是否为NULL
if ctm.Fstring == "" {
fmt.Println("FString is NULL")
} else {
fmt.Println("FString is NOT NULL")
}
if ctm.FNullString.Valid == false {
fmt.Println("FNullString is NULL")
} else {
fmt.Println("FNullString is NOT NULL")
}
}
2.5 字段标签设置字段属性
type FieldTag struct {
gorm.Model
//string类型处理
FStringDefault string `gorm:`
FTypeChar string `gorm:"type:char(32)"`
FTypeVarchar string `gorm:"type:varchar(255)"`
FTypeText string `gorm:"type:text"`
FTypeBlob []string `gorm:"type:blob"`
FTypeEnum string `gorm:"type:enum('GO','GORM','MySQL')"` //枚举
FTypeSet string `gorm:"type:set('GO','GORM','MySQL')"` //集合
FColNum string `gorm:"column:cus tom_column_name"`
//默认是NULL
FColNotNull string `gorm:"type:varchar(255);not null"`
FColDefault string `gorm:"type:varchar(255);default: gorm middle ware;"`
FColComment string `gorm:"type:varchar(255);comment:带有注释的字段"`
}
2.6 索引和约束管理
type IAndC struct {
//基础索引类型
ID uint `gorm:"primaryKey"`
Email string `gorm:"type:varchar(255);uniqueIndex"`
Age uint8 `gorm:"index;check:age >= 18 AND email is not null"`
//复合索引
FirstName string `gorm:"index:name"` //name的索引关联了两个字段0
LastName string `gorm:"index:name"`
//顺序关键顺序
//默认的 priority:10
FirstName1 string `gorm:"index:name1,priority:2"` //排序先last后first
LastName1 string `gorm:"index:name,priority:1"`
//索引选项,前缀长度,排序方式,comment
Height float32 `gorm:"index:,sort:desc"`
AddressHash string `gorm:"index:,length:12,comment:前12个字符作为索引关键字"`
}
2.7 字段操作控制
type Service struct {
gorm.Model
//Url `gorm:"-"` //该字段忽略 -:all 全部忽略
Url string `gorm:"-:migration"` //仅忽略迁移,可以做增删改查工作
Schema string
Host string
Path string
QueryString string
}
2.8 序列化器的使用
type Paper struct {
gorm.Model
Subject string
//Tags []string 不可以 unsupported data type: &[]
//使用 json 序列化器进行处理
Tags []string `gorm:"serializer:json"`
}
func PaperCrud() {
err := DB.AutoMigrate(&Paper{})
if err != nil {
log.Fatal(err)
}
//常规操作
paper := &Paper{}
paper.Subject = "使用serializer操作Tags字段"
paper.Tags = []string{"GO", "Serializer", "Gorm", "MySQL"}
//create 执行 序列化
if err := DB.Create(paper).Error; err != nil {
log.Fatal(err)
}
//查询执行 反序列化
newPaper := &Paper{}
DB.First(newPaper, 2)
fmt.Printf("%+v", newPaper)
}
2.9 自定义序列化器的实现
type Paper struct {
gorm.Model
Subject string
//Tags []string 不可以 unsupported data type: &[]
//使用 json 序列化器进行处理
Tags []string `gorm:"serializer:json"`
Categories []string `gorm:"serializer:csv"`
}
// 1.定义实现了序列号器接口的类型
type CSVSerializer struct{}
// 实现Scan unserialize 反序列化
/*
ctx Context对象
field 模型的字段对应的类型
dst 目标值(最终结果赋值到dst)
dbValue 从数据库读取的值
*/
func (CSVSerializer) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) error {
//初始化一个用来存储字段值的变量
var fieldValue []string
//一、解析读取到的数据表的数据
if dbValue != nil { //不是NULL
//支持解析的只有string和[]byte
//使用类型检测进行判定
var str string
switch v := dbValue.(type) {
case string:
str = v
case []byte:
str = string(v)
default:
return fmt.Errorf("failed to unmarshal CSV value :%#v", dbValue)
}
//二、核心,将数据表中的字段使用逗号分割,形成[]string
fieldValue = strings.Split(str, ",")
}
//三、将处理好的数据设置在dst上
field.ReflectValueOf(ctx, dst).Set(reflect.ValueOf(fieldValue))
return nil
}
// 实现Value serialize 序列化
// fieldValue 模型的字段值
func (CSVSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
//将字段值转换为可存储的CSV结构
return strings.Join(fieldValue.([]string), ","), nil
}
// 2.注册到GORM中
func CustomSerializer() {
// 注册序列化器
schema.RegisterSerializer("csv", CSVSerializer{})
//3.测试
err := DB.AutoMigrate(&Paper{})
if err != nil {
log.Fatal(err)
}
//常规操作
paper := &Paper{}
paper.Subject = "使用自定义操作categories字段"
paper.Categories = []string{"GO", "Serializer", "Gorm", "MySQL", "categories "}
paper.Tags = []string{"GO", "Serializer", "Gorm", "MySQL"}
//create 执行 序列化
if err := DB.Create(paper).Error; err != nil {
log.Fatal(err)
}
//查询执行 反序列化
newPaper := &Paper{}
DB.First(newPaper, paper.ID)
fmt.Printf("%+v", newPaper)
}
2.10 嵌入结构体和gorm.model
// 用于DB表交互的模型
type Blog struct {
gorm.Model
BlogBasic
Author `gorm:"embeddedPrefix:au thor_"`
}
type BlogBasic struct {
Subject string
Summary string
Content string
}
type Author struct {
Name string
Email string
}
2.10 小节
3 具体操作
3.1
3.2 错误处理
3.3 基于模型和map完成创建
type Content struct {
gorm.Model
Subject string
Likes uint
PublishTime *time.Time
}
func CreateBasic() {
DB.AutoMigrate(&Content{})
//模型映射记录,操作模型字段,就是操作记录的列
c1 := Content{}
c1.Subject = "GORM的使用"
//执行新增
result := DB.Create(&c1)
//处理错误
if result.Error != nil {
log.Fatal(result.Error)
}
//最新id,影响到记录数
fmt.Println(c1.ID, result.RowsAffected)
//通过 map 指定数据 新增记录
values := map[string]any{
"Subject": "通过map指定的值",
"PublishTime": time.Now(),
}
result2 := DB.Model(&Content{}).Create(values)
if result2.Error != nil {
log.Fatal(result2.Error)
}
//这个为什么输出不了id 原因:因为没有对模型进行创建,id不知道存放在哪里
fmt.Println(result2.RowsAffected)
}
- 使用map 自动更新创建时间 不会随之添加,只会在记录中添加map中有的数据
3.4 批量插入
func CreateMulti() {
DB.AutoMigrate(&Content{})
//定义模型的切片
models := []Content{
{Subject: "标题1"},
{Subject: "标题2"},
{Subject: "标题3"},
{Subject: "标题3-1"},
}
result := DB.Create(&models)
if result.Error != nil {
log.Fatal(result.Error)
}
fmt.Println("RowsAffected:", result.RowsAffected)
for _, m := range models {
fmt.Println("ID:", m.ID)
}
//切片结构同样支持
vs := []map[string]any{
{"Subject": "标题4"},
{"Subject": "标题5"},
{"Subject": "标题6"},
{"Subject": "标题6-1"},
}
result2 := DB.Model(&Content{}).Create(vs)
if result2.Error != nil {
log.Fatal(result2.Error)
}
fmt.Println("ROWS:", result2.RowsAffected)
}}
// 防止sql语句过长
func CreateBatches() {
DB.AutoMigrate(&Content{})
//定义模型的切片
models := []Content{
{Subject: "标题11"},
{Subject: "标题12"},
{Subject: "标题13"},
{Subject: "标题14"},
}
result := DB.CreateInBatches(&models, 2)
if result.Error != nil {
log.Fatal(result.Error)
}
fmt.Println("RowsAffected:", result.RowsAffected)
for _, m := range models {
fmt.Println("ID:", m.ID)
}
//切片结构同样支持
vs := []map[string]any{
{"Subject": "标题15"},
{"Subject": "标题16"},
{"Subject": "标题17"},
{"Subject": "标题18"},
}
result2 := DB.Model(&Content{}).CreateInBatches(vs, 2)
if result2.Error != nil {
log.Fatal(result2.Error)
}
fmt.Println("ROWS:", result2.RowsAffected)
}
3.5 UpSert更新插入
func UpSert() {
DB.AutoMigrate(&Content{})
c1 := Content{}
c1.Subject = "新标题1111"
c1.Likes = 10
DB.Create(&c1)
c2 := Content{}
c2.ID = c1.ID //冲突原因 主键重复
c2.Subject = "新标题*****"
c2.Likes = 200
DB.Create(&c1)
fmt.Println(c1)
//冲突时更新全部字段
if err := DB.
Clauses(clause.OnConflict{UpdateAll: true}).
Create(&c2).Error; err != nil {
log.Fatal(err)
}
//冲突后,更新部分字段
c4 := Content{}
c4.ID = c1.ID //冲突原因 主键重复
c4.Subject = "新标题456789" //c4 "新标题*****" 300
c4.Likes = 300
if err := DB.Clauses(clause.OnConflict{DoUpdates: clause.AssignmentColumns(
[]string{"likes"})}). //只更新likes字段
Create(&c4).
Error; err != nil {
log.Fatal(err)
}
}
3.6 创建时默认值的处理
type Content struct {
gorm.Model
Subject string
Likes uint `gorm:"default:99"`
Vikes *uint `gorm:"default:99"`
PublishTime *time.Time
}
func DefaultValue() {
DB.AutoMigrate(&Content{})
c1 := Content{}
c1.Subject = "新标题2222"
//下面两行 vikes字段输入数据库值为 0,而不是99,因为Vikes字段零值是 nil,而uint(0)有地址
vlikes := uint(0)
c1.Vikes = &vlikes
DB.Create(&c1)
}
3.7 创建时选择特定字段
func Omit() {
DB.AutoMigrate(&Content{})
c1 := Content{}
c1.Subject = "原始标题"
c1.Likes = 10
c1.Vikes = 99
now := time.Now()
c1.PublishTime = &now
//需要操作的字段有 Subject Likes UpdatedAt
DB.Select("Subject", "Likes", "UpdatedAt").Create(&c1)
//不需要操作的字段有 Subject UpdatedAt Vikes
DB.Omit("Subject", "UpdatedAt", "Vikes").Create(&c1)
}
3.8 创建时的钩子方法
func (c *Content) BeforeCreate(db *gorm.DB) error {
//业务
if c.PublishTime == nil {
now := time.Now()
c.PublishTime = &now
}
//配置
db.Statement.AddClause(clause.OnConflict{UpdateAll: true})
return nil
}
3.9 查询 基于主键的查询
type Content struct {
gorm.Model
Subject string
Likes uint `gorm:`
Vikes uint `gorm:`
PublishTime *time.Time
}
type ContentStrPK struct {
ID string `gorm:"primaryKey"`
Subject string
Likes uint
Views uint
PublishTime *time.Time
}
func GetByPk() {
//migrate
DB.AutoMigrate(&Content{}, &ContentStrPK{})
//查询单条
c := Content{}
if err := DB.First(&c, 10).Error; err != nil {
log.Println(err)
}
//字符串类型的主键
cStr := ContentStrPK{}
if err := DB.First(&cStr, "id = ?", "some pk").Error; err != nil {
log.Println(err)
}
//查询多条
var cs []Content
if err := DB.Find(&cs, []uint{10, 11, 12, 13}).Error; err != nil {
log.Println(err)
}
for _, v := range cs {
fmt.Println(v)
}
//字符串类型的主键
var cStrs []ContentStrPK
if err := DB.Find(&cStrs, "id IN ?", []string{"some", "key", "you"}).Error; err != nil {
log.Println(err)
}
}
3.10 查询单条
func GetONE() {
c := Content{}
if err := DB.First(&c, "id > ?", 19).Error; err != nil {
log.Println(err)
}
fmt.Println("first ", c)
l := Content{}
if err := DB.Last(&l, "id > ?", 19).Error; err != nil {
log.Println(err)
}
fmt.Println("Last ", l)
t := Content{}
if err := DB.Take(&t, "id > ?", 19).Error; err != nil {
log.Println(err)
}
fmt.Println("Take ", t)
f := Content{}
if err := DB.Limit(1).Find(&f, "id > ?", 19).Error; err != nil {
fmt.Println(err)
}
fmt.Println("Limit Find ", f)
fs := Content{}
if err := DB.Find(&fs, "id > ?", 19).Error; err != nil {
fmt.Println(err)
}
fmt.Println("find ", fs)
}
3.11 查询结果扫描到Map类型
func GetToMap() {
//单条
c := map[string]any{} //map[string]interface{}{}
if err := DB.Model(&Content{}).First(&c, 13).Error; err != nil {
log.Println(err)
}
fmt.Println(c["id"])
if c["id"].(uint) == 13 { //断言
fmt.Println("BING GO!")
}
//time类型的处理
fmt.Println(c["created_at"])
t, err := time.Parse("2006-01-02 15:04:05.000 -0700 CST", "2023-08-24 20:46:20.372 +0800 CST")
if err != nil {
log.Println(err)
}
if c["created_at"].(time.Time) == t {
fmt.Println("created_at time bingo !")
}
//多条
var cs []map[string]any
if err := DB.Model(&Content{}).Find(&cs, []uint{18, 19, 20}).Error; err != nil {
fmt.Println(err)
}
for _, v := range cs {
fmt.Println(v["id"], v["subject"])
}
}
3.12 查询单列
// 单个列
func GetPluck() {
//使用切片存储
var subjects []sql.NullString
if err := DB.Model(Content{}).Pluck("subject", &subjects).Error; err != nil {
fmt.Println(err)
}
for _, v := range subjects {
if v.Valid {
fmt.Println(v.String)
} else if v.Valid == false {
fmt.Println("{NULL}", v.Valid)
}
}
}
// 两个列连接
func GetPluckExp() {
//使用切片存储,如果表达式可以保证null不会出现,就可以不使用nulltype
var subjects []string
//字段为表达式的结果
if err := DB.Model(&Content{}).
Pluck("concat(coalesce(subject,'[no subject]'),'-',likes)", &subjects).Error; err != nil {
log.Println(err)
}
for _, v := range subjects {
fmt.Println(v)
}
}
3.13 字段选择子句
type Content struct {
gorm.Model
Subject string
Likes uint `gorm:`
Vikes uint `gorm:`
PublishTime *time.Time
//不需要迁移
//禁用写操作
Sv string `gorm:"-:migration;<-:false;"`
}
func GetSelect() {
var c Content
var cm map[string]any
//一、基本字段名
if err := DB.Select("subject", "likes").First(&c, 13).Error; err != nil {
log.Fatalln(err)
}
//二、字段表达式
//1.映射至结构体 需要定义结构体字段 Sv
if err := DB.Select("subject", "likes", "concat(subject,'-',vikes) AS sv").First(&c, 13).Error; err != nil {
log.Fatalln(err)
}
//2.映射至map map不需要定义结构体的字段 Sv
if err := DB.Model(&Content{}).Select("subject", "likes", "concat(subject,'-',vikes) AS sv").First(&cm, 13).Error; err != nil {
log.Fatalln(err)
}
fmt.Printf("%+v\n", cm)
}
3.14 去重选项distinct
func GetDistinct() {
var cs []Content
//基本字段名
if err := DB.Distinct("subject").Find(&cs).Error; err != nil { //Distinct第一个参数一般为 "*"(去重字段)
log.Fatalln(err)
}
for _, v := range cs {
fmt.Printf("%+v\n", v.Subject)
}
}
3.15 条件设置方法
func WhereMethod() {
var cs []Content
//1. inline条件,内联条件
if err := DB.Find(&cs, "likes > ? AND subject like ?", 90, "新标题%").Error; err != nil {
log.Fatalln(err)
}
//2.Where,通常在动态拼凑条件时使用 等同于 1
if err := DB.Where("likes > ? AND subject like ?", 90, "新标题%").Find(&cs).Error; err != nil {
log.Fatalln(err)
}
query := DB.Where("likes > ?", 90)
subject := "123"
if subject != "" { //当前用户输入subject,不为空字符串时,才拼凑subject条件
query.Where("subject like ?", "新标题%")
}
//3. OR 逻辑运算
query = DB.Where("likes > ?", 90)
subject = "新标题"
if subject != "" { //当前用户输入subject,不为空字符串时,才拼凑subject条件
//query.Where("subject like ?", subject+"%")
query.Or("subject like ?", subject+"%")
}
//4.Not 逻辑运算
query = DB.Where("likes > ?", 90)
subject = "新标题"
if subject != "" { //当前用户输入subject,不为空字符串时,才拼凑subject条件
//query.Where("subject like ?", subject+"%")
query.Not("subject like ?", subject+"%")
/*SELECT * FROM `msb_content` WHERE
(likes > 90 OR NOT subject like '新标题%')
AND `msb_content`.`deleted_at` IS NULL
*/
query.Or(DB.Not("subject like ?", subject+"%"))
}
if err := query.Find(&cs).Error; err != nil {
log.Fatalln(err)
}
}
3.16 条件语法规则
func WhereType() {
var cs []Content
//一、(1 or 2) and (3 and (4 or 5))
condA := DB.Where("subject < ?", 1).Or("subject > ?", 2)
condB := DB.Where("subject < ?", 3).Where(DB.Where("subject < ?", 4).Or("subject < ?", 5))
query := DB.Where(condA).Where(condB)
//SELECT * FROM `msb_content` WHERE (subject < 1 OR subject > 2)
//AND
//(subject < 3 AND (subject < 4 OR subject < 5)) AND `msb_content`.`deleted_at` IS NULL
//二、map构建条件,and,判断 =,in
query = DB.Where(map[string]any{
"views": 100,
"id": []uint{1, 2, 3, 4, 5},
})
//三、struct条件构建
query = DB.Where(Content{
Subject: "GORM",
Vikes: 100,
})
if err := query.Find(&cs).Error; err != nil {
log.Fatalln(err)
}
}
func PlaceHolder() {
var cs []Content
//一、匿名
query := DB.Where("likes = ? AND subject like ?", 100, "gorm%")
//二、2.1 具名,绑定名字sql.Named()结构
query = DB.Where("likes = @like AND subject like @subject",
sql.Named("subject", "gorm%"), sql.Named("like", 100))
//2.2、gorm还支持使用map的形式具名绑定
query = DB.Where("likes = @like AND subject like @subject",
map[string]any{
"subject": "gorm%",
"like": 100,
})
if err := query.Find(&cs).Error; err != nil {
log.Fatalln(err)
}
}
3.17 字段排序和表达式排序
func OrderBy() {
var cs []Content
ids := []uint{2, 3, 1}
//1.
query := DB.Order("FIELD(id,2,3,1)") //按2 3 1 顺序输出
//2.
query = DB.Clauses(clause.OrderBy{
Expression: clause.Expr{
SQL: "FIELD(id,?)",
Vars: []any{ids},
WithoutParentheses: true,
},
})
if err := query.Find(&cs, ids).Error; err != nil {
log.Fatalln(err)
}
for _, c := range cs {
fmt.Println(c.ID)
}
}
3.18 结果限定及分页
// 定义分页必要的数据结构
// 默认的值
const (
DefaultPage = 1
DefaultPageSize = 12
)
// 定义分页必要的数据结构
type Pager struct {
Page, PageSize int
}
// 翻页程序
func Pagination(pager Pager) {
//确定 offset 和 pagesize
page := DefaultPage
if pager.Page != 0 {
page = pager.Page
}
pagesize := DefaultPageSize
if pager.PageSize != 0 {
pagesize = pager.PageSize
}
//计算 offset
//page pagesize offset
//1 10 0
//2 10 10
//3 10 20
offset := pagesize * (page - 1)
var cs []Content
if err := DB.Offset(offset).Limit(pagesize).Find(&cs); err != nil { //偏移量,页大小
log.Println(err)
}
for _, v := range cs {
fmt.Println(v.ID)
}
}
// 用于得到func(db *gorm.DB) *gorm.DB类型函数
// 为什么不直接定义函数,因为需要func(db *gorm.DB) *gorm.DB与分页产生联系
func Paginate(pager Pager) func(db *gorm.DB) *gorm.DB {
page := DefaultPage
if pager.Page != 0 {
page = pager.Page
}
pagesize := DefaultPageSize
if pager.PageSize != 0 {
pagesize = pager.PageSize
}
offset := pagesize * (page - 1)
return func(db *gorm.DB) *gorm.DB {
//使用闭包的变量,实现翻页的业务逻辑
return db.Offset(offset).Limit(pagesize)
}
}
// 测试重用代码
func PaginationScope(pager Pager) {
var cs []Content
if err := DB.Scopes(Paginate(pager)).Find(&cs).Error; err != nil {
log.Println(err)
}
for _, v := range cs {
fmt.Println(v.ID)
}
}
3.19 分组聚合过滤
func GroupHaving() {
DB.AutoMigrate(&Content{})
//定义查询结构类型
type Result struct {
//分组字段
AuthorID uint
//合计字段
TotalViews int
TotalLikes int
AvgViews float64
}
//执行分组合计过滤查询
var re []Result
if err := DB.Model(&Content{}).
//SELECT `author_id`,SUM(vikes) as total_views,SUM(likes) as total_likes,AVG(vikes) as avg_views
//FROM `msb_content` WHERE `msb_content`.`deleted_at` IS NULL
//GROUP BY `author_id` HAVING total_views > 99
Select("author_id", "SUM(vikes) as total_views", "SUM(likes) as total_likes", "AVG(vikes) as avg_views").
Group("author_id").Having("total_views > ?", 99).
Find(&re).Error; err != nil {
log.Fatalln(err)
}
//SQL
}
3.20 count合计
func Count(pager Pager) {
//集中的条件,用于统计数量和获取某页记录
query := DB.Model(&Content{}).
Where("likes > ?", 99)
//total rows count
var count int64
if err := query.Count(&count).Error; err != nil {
log.Fatalln(err)
}
// 计算总页数 ceil(count / pagesize)
//row per page
var cs []Content
if err := query.Scopes(Paginate(pager)).Find(&cs).Error; err != nil {
log.Println(err)
}
for _, v := range cs {
fmt.Println(v.ID)
}
}
3.21 迭代查询
func Iterator() {
//利用DB.Rows() 获取 Rows对象
rows, err := DB.Model(&Content{}).Rows()
if err != nil {
log.Fatalln(err)
}
//注意:保证使用过后关闭rows结果集
defer func() {
_ = rows.Close()
}()
//迭代的从Rows中扫描记录到模型
for rows.Next() {
//还有记录存在于结果集中
var c Content
if err := DB.ScanRows(rows, &c); err != nil {
log.Fatalln(err)
}
fmt.Println(c.Subject)
}
}
3.22 锁子句
func Locking() {
var cs []Content
if err := DB.
Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&cs).Error; err != nil {
log.Fatalln(err)
}
if err := DB.
Clauses(clause.Locking{Strength: "SHARE"}).
Find(&cs).Error; err != nil {
log.Fatalln(err)
}
}
3.23 子查询
func SubQuery() {
//migrate
DB.AutoMigrate(&Author{}, &Content{})
//条件型子查询
//select * from content where author_id in (select id from author where status=0);
//子查询,不需要使用终结方法Find完成查询,只需要构建语句即可
whereSubQuery := DB.Model(&Author{}).Select("id").Where("status = ?", 0)
var cs []Content
if err := DB.Where("author_id IN (?)", whereSubQuery).Find(&cs).Error; err != nil {
log.Fatalln(err)
}
//from型子查询
//select * from (select subject,likes from content where publish_time is null) as temp where likes > 10);
fromSubQuery := DB.Model(&Content{}).Select("subject", "likes").Where("publish_time IS NULL")
type Result struct {
Subject string
Likes int
}
var rs []Result
if err := DB.Table("(?) AS temp", fromSubQuery).
Where("likes > ?", 10).
Find(&rs).Error; err != nil {
log.Fatalln(err)
}
}
3.24 钩子方法 查询钩子
func (c *Content) AfterFind(db *gorm.DB) error {
if c.AuthorID == 0 {
c.AuthorID = 1 //1.假定的默认作者
}
return nil
}
func FindHook() {
var c Content
if err := DB.First(&c, 13).Error; err != nil {
log.Fatalln(err)
}
fmt.Printf("%+v\n", c)
}
- AuthorID值赋为1
3.25 更新-主键条件更新及获取更新记录数
func UpdatePK() {
var c Content
//无主键
if err := DB.Save(&c).Error; err != nil {
log.Fatalln(err)
}
fmt.Println("1:", c.ID)
//具有主键id
if err := DB.Save(&c).Error; err != nil {
log.Fatalln(err)
}
fmt.Println("2:", c.ID)
}
func UpdateWhere() {
// 更新的字段值数据
// map推荐
values := map[string]any{
"subject": "Where Update Row",
"likes": 10001,
}
//执行带有条件的更新
result := DB.Model(&Content{}).
Omit("updated_at"). //操作时 忽略该字段
Where("likes > ?", 100).
Updates(values)
if result.Error != nil {
log.Fatalln(result.Error)
}
//获取更新结果,更新的记录数量(受影响的记录数)
//修改的记录数,而不是满足条件的记录数
log.Println("updated rows num: ", result.RowsAffected)
}
func UpdateNOWhere() {
// 更新的字段值数据
// map推荐
values := map[string]any{
"subject": "Where Update Row",
"likes": 101,
}
//执行带有条件的更新
result := DB.Model(&Content{}).
Omit("updated_at"). //操作时 忽略该字段
Where("1").
Updates(values)
if result.Error != nil {
log.Fatalln(result.Error)
}
//获取更新结果,更新的记录数量(受影响的记录数)
//修改的记录数,而不是满足条件的记录数
log.Println("updated rows num: ", result.RowsAffected)
}
3.26 更新-表达式值
func UpdateExpr() {
// 更新的字段值数据
// map推荐
values := map[string]any{
"subject": "Where Update Row",
//值为表达式计算的结果时,使用Expr类型
"likes": gorm.Expr("likes + ?", 10),
}
//执行带有条件的更新
result := DB.Model(&Content{}).
Omit("updated_at"). //操作时 忽略该字段
Where("likes > ?", 100).
Updates(values)
if result.Error != nil {
log.Fatalln(result.Error)
}
//获取更新结果,更新的记录数量(受影响的记录数)
//修改的记录数,而不是满足条件的记录数
log.Println("updated rows num: ", result.RowsAffected)
}
3.27 更新-Hook简单介绍
3.28 删除-基本主键条件删除操作
func DeleteWhere() {
result1 := DB.Delete(&Content{}, "likes < ?", 100)
if err := result1.Error; err != nil {
log.Fatalln(result1.Error)
}
result2 := DB.Where("likes < ?", 100).Delete(&Content{})
if err := result2.Error; err != nil {
log.Fatalln(err)
}
}
3.29 删除-软删除和永久删除
func FindDeleted() {
var c Content
DB.Delete(&c, 13)
//if err := DB.First(&c, 13).Error; err != nil {
// log.Fatalln(err)
//}
//fmt.Println(c)
if err := DB.Unscoped().First(&c, 13).Error; err != nil {
log.Fatalln(err)
}
/*
unscoped: {{13 2023-08-24 20:46:20.372 +0800 CST 2023-08-24 20:46:20.372 +0800 CST {2023-08-28 21:38:25.085 +0800 CST true}} 标题14 0 99 2023-08-24 17:12:56.788
+0800 CST 1}
*/
fmt.Println("unscoped:", c)
}
func DeleteHard() {
var c Content
if err := DB.Unscoped().Delete(&c, 14).Error; err != nil {
log.Fatalln(err)
}
}
3.30 原生sql的执行和结果处理
// 原生查询测试
func RawSelect() {
// 结果类型
type Result struct {
ID uint
Subject string
Likes, Vikes int
}
var rs []Result
//SQL
sql := "SELECT `id`,`subject`,`likes`,`vikes` FROM `msb_content` WHERE `likes` > ? ORDER BY `likes` DESC LIMIT ?"
//执行SQL,并扫描结果
if err := DB.Raw(sql, 99, 12).Scan(&rs).Error; err != nil {
log.Fatalln(err)
}
log.Println(rs)
}
// 执行类的SQL原生
func RawExec() {
//SQL
sql := "UPDATE `msb_content` SET `subject` = CONCAT(`subject`,'-new postfix') WHERE `id` BETWEEN ? AND ?"
//执行,获取结果
result := DB.Exec(sql, 20, 30)
if result.Error != nil {
log.Fatalln(result.Error)
}
log.Println(result.RowsAffected)
}
// sql.Row 或 sql.Rows 类型的结果处理
func RowAndRows() {
//SQL
sql := "SELECT `id`,`subject`,`likes`,`vikes` FROM `msb_content` WHERE `likes` > ? ORDER BY `likes` DESC LIMIT ?"
//执行,获取rows
rows, err := DB.Raw(sql, 99, 12).Rows()
if err != nil {
log.Fatalln(err)
}
//遍历rows
for rows.Next() {
//扫描的列独立的变量
//var id uint
//var subject string
//var likes, vikes int
//rows.Scan(&id, &subject, &likes, &vikes)
//fmt.Println(id, subject, likes, vikes)
//扫描到整体结构体
type Result struct {
ID uint
Subject string
Likes, Vikes int
}
var r Result
DB.ScanRows(rows, &r)
fmt.Println(r)
}
}
3.31 会话模式的基本使用
func SessionIssue() {
db := DB.Model(&Content{}).Where("vikes > ?", 100)
db.Where("likes > ?", 99)
//SELECT * FROM `msb_content` WHERE vikes > 100 AND likes > 99 AND `msb_content`.`deleted_at` IS NULL
var cs []Content
db.Find(&cs)
}
func SessionNew() {
// 需要重复使用的部分
// 将Session方法前的配置,记录到了当前的会话中
// 后边再次调用db方法直到终结方法,会保持会话中的子句选项
// 执行完终结方法后,再次调用db的方法,可以重用
db := DB.Model(&Content{}).Where("vikes > ?", 100).Session(&gorm.Session{})
var cs1 []Content
// SELECT * FROM `msb_content` WHERE vikes > 100 AND likes > 9 AND `msb_content`.`deleted_at` IS NULL
db.Where("likes > ?", 9).Find(&cs1)
var cs2 []Content
// SELECT * FROM `msb_content` WHERE vikes > 100 AND likes > 99 AND `msb_content`.`deleted_at` IS NULL
db.Where("likes > ?", 99).Find(&cs2)
}
3.32 会话模式的常用实操选项
func SessionOptions() {
// Skip Hook
db:=DB.Session(&gorm.Session{
SkipHooks:true,
})
db.Save(&Content{Subject: "no create hook"})
// Dry Run 生成SQL,但不执行
db := DB.Session(&gorm.Session{
DryRun: true,
})
stmt := db.Save(&Content{}).Statement
fmt.Println(stmt.SQL.String())
fmt.Println(stmt.Vars)
//prepare 预编译 编译重用 编译独立
//SQL 编译 绑定数据 执行
//预编译,将编译的过程缓存起来,便于重用
//在执行结构相同的SQL时,可以重用。
db := DB.Session(&gorm.Session{
PrepareStmt: true,
})
}
3.33 context上下文支持
func ContextTimeoutCancel() {
// 设置一个定时Cancel的Context 执行时间超过time.Millisecond会取消操作
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
defer cancel()
// 传递Context执行
var cs []Content
if err := DB.WithContext(ctx).Limit(5).Find(&cs).Error; err != nil {
log.Fatalln(err)
}
fmt.Println(cs)
}