之前写过一篇 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.Id
和 Student.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:ClassId1
的 json 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
必须存在goDB.Debug().Create(&Student{ Name: "李四", Age: 10, ClassId: 1, // 如果 classId 不存在会报错 })
对应的
sql
语句sqlINSERT INTO `student` (`name`,`age`,`class_id`) VALUES ('李四',10,1)
-
创建
student
时同时创建class
goDB.Debug().Create(&Student{ Name: "李四", Age: 10, Class: Class{ Name: "一年级2班", }, })
对应的
sql
语句sqlINSERT INTO `class` (`name`) VALUES ('一年级2班') ON DUPLICATE KEY UPDATE `id`=`id` INSERT INTO `student` (`name`,`age`,`class_id`) VALUES ('李四',10,2)
-
先查询已存在的数据
goc := Class{} DB.Debug().Take(&c, 1) DB.Debug().Create(&Student{ Name: "小红", Age: 10, Class: c, })
对应的
sql
语句sqlSELECT * 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{})
所以有两种方法:
-
删除反向引用
gotype Tag struct { ID uint Name string // Articles []Article `gorm:"many2many:article_tags"` // 反向引用 }
-
Tag
表也进行自定义连接goDB.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
Create
,CreateInBatches
,Save
是会运行
go
func (u *Student) BeforeCreate(tx *gorm.DB) error {
u.Age = 18
return nil
}
错误处理
go
err := DB.Task(&Student{}).Error
// 错误类型判断
err == Gorm.ErrRecordNotFound