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
相关推荐
Iced_Sheep35 分钟前
干掉 if else 之策略模式
后端·设计模式
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶1 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
先天牛马圣体2 小时前
如何提升大型AI模型的智能水平
后端
java亮小白19972 小时前
Spring循环依赖如何解决的?
java·后端·spring
2301_811274312 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
草莓base3 小时前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
Ljw...3 小时前
表的增删改查(MySQL)
数据库·后端·mysql·表的增删查改