gorm 基本使用 2

之前写过一篇 gorm 基本使用,这篇写的更加详细

数据库连接时的一些配置

gorm 连接数据库时,可以配置一些参数,如下:

go 复制代码
var dsn = "root:123456@tcp(go-uccs-1:3306)/zero_study?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
  SkipDefaultTransaction: true, // 跳过默认事务
  NamingStrategy: schema.NamingStrategy{
    TablePrefix:   "f_", // 表名前缀
    SingularTable: true, // 表名单数
    NoLowerCase: true,  // 表名不转小写
  },
})

log 配置

gorm 默认只打印错误和慢 SQL,默认是不展示 log,配置 log 有三种方式

全局配置

go 复制代码
var log = logger.Default.LogMode(logger.Info)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
  Logger: log,
})

局部配置

go 复制代码
db = db.Session(&gorm.Session{
  Logger: log,
})

db 对象配置

go 复制代码
db.Debug().AutoMigrate(&Student{})

gorm json tag

  • type: 定义字段类型
  • size: 定义字段大小
  • column: 自定义列名
  • primaryKey: 将列定义为主键
  • autoIncrement: 将列定义为自增
  • foreignKey: 定义外键关联
  • unique: 将列定义为唯一键
  • default: 定义列的默认值
  • not null: 定义列为非空
  • comment: 定义列的注释
  • embedded: 嵌入结构体
  • embeddedPrefix: 嵌入结构体的前缀
  • many2many: 定义多对多关系
  • joinForeignKey: 外键关联,修改主键名
  • joinReferences: 外键关联,修改引用名

Create

指针不传值,默认是 null,其他类型,不传值,默认是零值

go 复制代码
db.Create(&Student{Name: "zero", Age: 18})

对应的 sql 语句

sql 复制代码
INSERT INTO `student` (`name`,`age`) VALUES ('uccs',123)

批量

go 复制代码
students := []Student{{Name: "zero", Age: 18}, {Name: "uccs", Age: 183}}
DB.Create(&students)

对应的 sql 语句

sql 复制代码
INSERT INTO `student` (`name`,`age`) VALUES ('zero',18),('uccs',183)

Take

查询数据库中第一条数据

go 复制代码
student := Student{}
DB.Take(&student)

对应的 sql 语句

sql 复制代码
SELECT * FROM `student` LIMIT 1

第二个参数

Take 第二个参数是主键查询

go 复制代码
DB.Debug().Take(&student, 2)

对应的 sql 语句

sql 复制代码
SELECT * FROM `student` WHERE `student`.`id` = '2' LIMIT 1

Find

查询所有数据,如果结构体不是 slice 类型,会获取第一条数据,如果是 slice 类型,会获取所有数据

go 复制代码
student := Student{}
DB.Find(&student)  // 获取第一条数据

students := []Student{}
DB.Find(&students) // 获取所有数据

对应的 sql 语句

sql 复制代码
SELECT * FROM `student`

Update 和 Updates

go 复制代码
student := []Student{}
DB.Take(&student, 1).Update("name", "uccs") // 更新一条记录的一个属性

DB.Debug().Take(&student, 1).Updates(&Student{Name: "astak", Age: 16}) // 更新一条记录的多个属性

对应的 sql 语句

sql 复制代码
UPDATE `student` SET `name`='uccs' WHERE `student`.`id` = 1 AND `id` = 1 LIMIT 1

Delete

go 复制代码
student := Student{}
DB.Debug().Delete(&student, 1)

对应的 sql 语句

sql 复制代码
DELETE FROM `student` WHERE `student`.`id` = 1

Where

go 复制代码
student := &Student{}
DB.Debug().Where("name = ?", "zero").Find(&student)
// 等同于
DB.Debug().Find(&student, "name=?", "uccs")

对应的 sql 语句

sql 复制代码
SELECT * FROM `student` WHERE name = 'zero'

Select

Select 选择需要查询的字段

go 复制代码
student := &Student{}
DB.Debug().Select("name").Find(&student)

对应的 sql 语句

sql 复制代码
SELECT `name` FROM `student`

Scan

Scan 是根据 json tag 来选择字段

go 复制代码
s := []struct {
  Name string `gorm:"column:name"`
}{}
student := &Student{}
DB.Debug().Model(student).Select("name").Scan(&s)

对应的 sql 语句

sql 复制代码
SELECT `name` FROM `student`

Order

go 复制代码
student := &[]Student{}

对应的 sql 语句

sql 复制代码
SELECT * FROM `student` ORDER BY age desc

Limit 和 Offset

go 复制代码
student := &[]Student{}
DB.Debug().Limit(2).Offset(2).Find(student)

对应的 sql 语句

sql 复制代码
SELECT * FROM `student` LIMIT 2 OFFSET 2

Distinct

go 复制代码
ageList := []int{}
DB.Debug().Model(&Student{}).Select("age").Distinct("age").Scan(&ageList)
// 效果是一样的
DB.Debug().Model(student).Select("distinct age").Scan(&ageList)

对应的 sql 语句

sql 复制代码
SELECT DISTINCT `age` FROM `student`

Group

go 复制代码
g := []struct {
  Count int
  Name  string
}{}
DB.Debug().Model(&Student{}).Select("count(name) as count, name").Group("name").Scan(&g)

对应的 sql 语句

sql 复制代码
SELECT count(name) as count, name FROM `student` GROUP BY `name`
go 复制代码
g := []struct {
  Count    int
  Name     string
  NameList string
}{}
DB.Debug().Model(&Student{}).Select("group_concat(name) as name_list, count(name) as count, name").Group("name").Scan(&g)

对应的 sql 语句

sql 复制代码
SELECT group_concat(name) as name_list, count(name) as count, name FROM `student` GROUP BY `name`

Raw

Raw 执行原生 sql

go 复制代码
s := []struct {
  Id   int
  Name string
  Age  int
}{}
DB.Raw("SELECT * FROM student WHERE id = ?", 6).Scan(&s)

Scopes

go 复制代码
s := []Student{}
DB.Debug().Model(&Student{}).Scopes(GreaterThan).Find(&s)

子查询

go 复制代码
s := []Student{}
DB.Debug().Model(&Student{}).Where("age > (?)", DB.Model(&Student{}).Select("avg(age) as age")).Find(&s)

对应的 sql 语句

sql 复制代码
SELECT * FROM `student` WHERE age > (SELECT avg(age) as age FROM `student`)

具名参数

go 复制代码
DB.Raw("SELECT * FROM student_table WHERE id = @id", sql.Named("id", 6)).Scan(&s)
DB.Raw("SELECT * FROM student_table WHERE id = @id", map[string]any{"id": 6}).Scan(&s)

一对多

Class.IdStudent.ClassId 是外键关联,他们的 json tag 必须一致

外键关联:表名 + Id

go 复制代码
type Class struct {
  Id       int `gorm:"primaryKey;autoIncrement;"`
  Name     string
  Students []Student
}
type Student struct {
  Id      int `gorm:"primaryKey;autoIncrement"`
  Name    string
  Age     int
  ClassId int
  Class   Class
}
db.AutoMigrate(&Student{}, &Class{})

重写外键关联

两边都要加上 foreignKey:ClassId1json tag

go 复制代码
type Class struct {
  Id       int        `gorm:"primaryKey;autoIncrement;"`
  Name     string
  Students []Student
}
type Student struct {
  Id       int `gorm:"primaryKey;autoIncrement"`
  Name     string
  Age      int
  ClassId int
  Class    Class
}
db.AutoMigrate(&Student{}, &Class{})

创建 class 带上 student

go 复制代码
DB.Debug().Create(&Class{
  Name: "一年级1班",
  Students: []Student{
    {Name: "uccs", Age: 8},
    {Name: "astak", Age: 7},
  },
})

对应的 sql 语句

sql 复制代码
INSERT INTO `student` (`name`,`age`,`class_id`) VALUES ('uccs',8,1),('astak',7,1) ON DUPLICATE KEY UPDATE `class_id`=VALUES(`class_id`)
INSERT INTO `class` (`name`) VALUES ('一年级1班')

创建 student 关联 class

  • classId 必须存在

    go 复制代码
    DB.Debug().Create(&Student{
      Name:    "李四",
      Age:     10,
      ClassId: 1,  // 如果 classId 不存在会报错
    })

    对应的 sql 语句

    sql 复制代码
    INSERT INTO `student` (`name`,`age`,`class_id`) VALUES ('李四',10,1)
  • 创建 student 时同时创建 class

    go 复制代码
    DB.Debug().Create(&Student{
      Name: "李四",
      Age:  10,
      Class: Class{
        Name: "一年级2班",
      },
    })

    对应的 sql 语句

    sql 复制代码
    INSERT INTO `class` (`name`) VALUES ('一年级2班') ON DUPLICATE KEY UPDATE `id`=`id`
    INSERT INTO `student` (`name`,`age`,`class_id`) VALUES ('李四',10,2)
  • 先查询已存在的数据

    go 复制代码
    c := Class{}
    DB.Debug().Take(&c, 1)
    DB.Debug().Create(&Student{
      Name:  "小红",
      Age:   10,
      Class: c,
    })

    对应的 sql 语句

    sql 复制代码
    SELECT * FROM `class` WHERE `class`.`id` = 1 LIMIT 1
    INSERT INTO `class` (`name`,`id`) VALUES ('一年级1班',1) ON DUPLICATE KEY UPDATE `id`=`id`
    INSERT INTO `student` (`name`,`age`,`class_id`) VALUES ('小红',10,1)

给 class 添加 student

go 复制代码
c := Class{}
DB.Debug().Take(&c, 1) // 查询 Class
s := Student{}
DB.Debug().Take(&s, 8) // 查询 Student
// 给 class 的 Students 添加 student
DB.Debug().Model(&c).Association("Students").Append(&s)

对应的 sql 语句

sql 复制代码
SELECT * FROM `class` WHERE `class`.`id` = 1 LIMIT 1
SELECT * FROM `student` WHERE `student`.`id` = 8 LIMIT 1
INSERT INTO `student` (`name`,`age`,`class_id`,`id`) VALUES ('王五',3,1,8) ON DUPLICATE KEY UPDATE `class_id`=VALUES(`class_id`)

给 student 添加 class

go 复制代码
c := Class{}
DB.Debug().Take(&c, 1)
s := Student{}
DB.Debug().Take(&s, 8)
DB.Debug().Model(&s).Association("Class").Append(&c)

对应的 sql 语句

sql 复制代码
SELECT * FROM `class` WHERE `class`.`id` = 1 LIMIT 1
SELECT * FROM `student` WHERE `student`.`id` = 8 LIMIT 1
INSERT INTO `class` (`name`,`id`) VALUES ('一年级1班',1) ON DUPLICATE KEY UPDATE `id`=`id`
UPDATE `student` SET `class_id`=1 WHERE `id` = 8

预加载

go 复制代码
s := Student{}
DB.Debug().Preload("Class").Take(&s)

对应的 sql 语句

sql 复制代码
SELECT * FROM `class` WHERE `class`.`id` = 1
SELECT * FROM `student` LIMIT 1

带条件的预加载

go 复制代码
s := Student{}
DB.Debug().Preload("Class", "id = ?", 2).Take(&s)

对应的 sql 语句

sql 复制代码
SELECT * FROM `class` WHERE `class`.`id` = 1 AND id = 2
SELECT * FROM `student` LIMIT 1

自定义预加载

go 复制代码
s := Student{}
DB.Debug().Preload("Class", func(db *gorm.DB) *gorm.DB {
  return db.Where("id = ?", 2)
}).Take(&s)

对应的 sql 语句

sql 复制代码
SELECT * FROM `class` WHERE `class`.`id` = 1 AND id = 2
SELECT * FROM `student` LIMIT 1

删除

go 复制代码
c := Class{}
DB.Debug().Preload("Students").Take(&c, 1)
DB.Debug().Model(&c).Association("Students").Delete(&c.Students)

对应的 sql 语句

sql 复制代码
SELECT * FROM `student` WHERE `student`.`class_id` = 1
SELECT * FROM `class` WHERE `class`.`id` = 1 LIMIT 1
UPDATE `student` SET `class_id`=NULL WHERE `student`.`class_id` = 1 AND `student`.`id` IN (1,2,3,4,7,8)

一对一

go 复制代码
type User struct {
  ID       uint
  Name     string
  Age      int
  Gender   bool
  UserInfo UserInfo
}
type UserInfo struct {
  UserID uint
  User   *User
  ID     uint
  Addr   string
  Like   string
}
db.AutoMigrate(&UserInfo{}, &User{})

多对多

go 复制代码
type Tag struct {
  ID       uint
  Name     string
  Articles []Article `gorm:"many2many:article_tags"`
}
type Article struct {
  ID    uint
  Title string
  Tags  []Tag `gorm:"many2many:article_tags"`
}
db.AutoMigrate(&Article{}, &Tag{})

更新(先删除再添加)

go 复制代码
// 先删除
a := Article{}
DB.Debug().Preload("Tags").Take(&a, 2)
DB.Model(&a).Association("Tags").Delete(a.Tags)
// 再更新
t := Tag{}
DB.Debug().Take(&t, 1)
DB.Debug().Model(&a).Association("Tags").Append(&t)

对应的 sql 语句

sql 复制代码
-- 先删除
SELECT * FROM `article_tags` WHERE `article_tags`.`article_id` = 2
SELECT * FROM `tag` WHERE `tag`.`id` IN (3,4)
SELECT * FROM `article` WHERE `article`.`id` = 2 LIMIT 1
DELETE FROM `article_tags` WHERE `article_tags`.`article_id` = 2 AND `article_tags`.`tag_id` = 1
-- 再更新
SELECT * FROM `tag` WHERE `tag`.`id` = 1 LIMIT 1
INSERT INTO `tag` (`name`,`id`) VALUES ('python',1) ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `article_tags` (`article_id`,`tag_id`) VALUES (2,1) ON DUPLICATE KEY UPDATE `article_id`=`article_id`

更新(替换)

go 复制代码
a := Article{}
DB.Debug().Preload("Tags").Take(&a, 2)
t := Tag{}
DB.Debug().Take(&t, 6)
DB.Debug().Model(&a).Association("Tags").Replace(&t)

对应的 sql 语句

sql 复制代码
SELECT * FROM `article_tags` WHERE `article_tags`.`article_id` = 2
SELECT * FROM `tag` WHERE `tag`.`id` = 1
SELECT * FROM `article` WHERE `article`.`id` = 2 LIMIT 1
SELECT * FROM `tag` WHERE `tag`.`id` = 6 LIMIT 1
INSERT INTO `tag` (`name`,`id`) VALUES ('后端',6) ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `article_tags` (`article_id`,`tag_id`) VALUES (2,6) ON DUPLICATE KEY UPDATE `article_id`=`article_id`
DELETE FROM `article_tags` WHERE `article_tags`.`article_id` = 2 AND `article_tags`.`tag_id` <> 6

自定义连接表

go 复制代码
type Tag struct {
	ID       uint
	Name     string
	Articles []Article `gorm:"many2many:article_tags"` // 反向引用
}
type Article struct {
	ID    uint
	Title string
	Tags  []Tag `gorm:"many2many:article_tags"`
}
type ArticleTag struct {
	ArticleID uint      `gorm:"primaryKey"`
	TagID     uint      `gorm:"primaryKey"`
	CreatedAt  time.Time `json:"created_at"`
}

这里存在反向引用,所以第三张表中 create_at 没法创建

go 复制代码
DB.Debug().SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
DB.AutoMigrate(&Article{}, &Tag{}, &ArticleTag{})

所以有两种方法:

  1. 删除反向引用

    go 复制代码
    type Tag struct {
      ID       uint
      Name     string
      // Articles []Article `gorm:"many2many:article_tags"` // 反向引用
    }
  2. Tag 表也进行自定义连接

    go 复制代码
    DB.Debug().SetupJoinTable(&Tag{}, "Articles", &ArticleTags{})

添加文章并添加标签,自动关联

ArticleTag 表中 CreatedAt 字段在数据创建时,会自动填充上时间,如果是其他字段,需要用 hook 函数 BeforeCreate 来填充

go 复制代码
DB.Debug().Create(&Article{
  Title: "flask基础",
  Tags:  []Tag{{Name: "python"}, {Name: "flask"}},
})

对应的 sql 语句

sql 复制代码
INSERT INTO `tag` (`name`) VALUES ('python'),('flask') ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `article_tag` (`article_id`,`tag_id`,`created_at`) VALUES (1,1,'2024-05-29 20:11:20.973'),(1,2,'2024-05-29 20:11:20.973') ON DUPLICATE KEY UPDATE `article_id`=`article_id`
INSERT INTO `article` (`title`) VALUES ('flask基础')

添加文章,关联已有标签

go 复制代码
t := []Tag{}
DB.Debug().Find(&t, "name in ?", []string{"python"})
DB.Debug().Create(&Article{
  Title: "Diango基础",
  Tags:  t,
})

对应的 sql 语句

sql 复制代码
SELECT * FROM `tag` WHERE name in ('python')
INSERT INTO `tag` (`name`,`id`) VALUES ('python',1) ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `article_tag` (`article_id`,`tag_id`,`created_at`) VALUES (2,1,'2024-05-29 20:19:29.048') ON DUPLICATE KEY UPDATE `article_id`=`article_id`
INSERT INTO `article` (`title`) VALUES ('Diango基础')

给已有的文章关联标签

go 复制代码
t := []Tag{}
DB.Debug().Find(&t, "name in ?", []string{"golang"})
a := Article{}
DB.Debug().Take(&a, "title = ?", "golang基础")
DB.Debug().Model(&a).Association("Tags").Append(t)

对应的 sql 语句

sql 复制代码
SELECT * FROM `tag` WHERE name in ('golang')
SELECT * FROM `article` WHERE title = 'golang基础' LIMIT 1
INSERT INTO `tag` (`name`,`id`) VALUES ('golang',3) ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `article_tag` (`article_id`,`tag_id`,`created_at`) VALUES (3,3,'2024-05-29 20:24:25.459') ON DUPLICATE KEY UPDATE `article_id`=`article_id`

存储 json 数据

go 复制代码
type Auth struct {
  ID   uint
  Info Info `gorm:"type:string"`  // 不指定 type 默认是 blob
}
type Info struct {
  Status string `json:"status"`
  Addr   string `json:"addr"`
  Age    uint   `json:"age"`
}

func (i *Info) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprintf("failed to unmarshal json value: ", value))
  }
  return json.Unmarshal(bytes, i)
}
func (i Info) Value() (driver.Value, error) {
  return json.Marshal(i)
}

DB.Debug().Create(&Auth{
  Info: Info{
    Status: "success",
    Addr:   "上海",
    Age:    18,
  },
})
auth := Auth{}
DB.Take(&auth, 1)

对应的 sql 语句

sql 复制代码
INSERT INTO `auth` (`info`) VALUES ('{"status":"success","addr":"上海","age":18}')
SELECT * FROM `auth` WHERE `auth`.`id` = 1 LIMIT 1

枚举

go 复制代码
func (s Status) MarshalJSON() ([]byte, error) {
  return json.Marshal(string(s))
}
func (s Status) String() string {
  var str string
  switch s {
  case Running:
    str = "Running"
  case Offline:
    str = "Offline"
  case Except:
    str = "Except"
  }
  return str
}
type Host struct {
  ID     uint
  IP     string
  Status Status `gorm:"size:8"`
}

DB.Debug().Create(&Host{
  IP:     "192.168.1.1",
  Status: Running,
})

h := Host{}
DB.Take(&h)
d, _ := json.Marshal(&h)
fmt.Println(string(d))

hook

CreateCreateInBatches,Save 是会运行

go 复制代码
func (u *Student) BeforeCreate(tx *gorm.DB) error {
	u.Age = 18
	return nil
}

错误处理

go 复制代码
err := DB.Task(&Student{}).Error
// 错误类型判断
err == Gorm.ErrRecordNotFound
相关推荐
wowocpp1 小时前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go1 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf2 小时前
go语言学习进阶
后端·学习·golang
全栈派森4 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse4 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭5 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架5 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱5 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
☞无能盖世♛逞何英雄☜5 小时前
Flask框架搭建
后端·python·flask
进击的雷神5 小时前
Perl语言深度考查:从文本处理到正则表达式的全面掌握
开发语言·后端·scala