本文是青训营课程-后端入门-Go语言原理与实践《Go 框架三件套详解(Web/RPC/ORM)》部分的总结,讲师为李龙。
内容挺多,我记不全,具体还要查询官方文档gorm.cn/zh_CN/docs/。
定义模型
定义gorm model,gorm model对应数据库中的一张表
type User struct {
gorm.Model
Name string// name列
Password string// password列
}
这种不使用gorm tag的方式,结构体的字段名会自动映射到表的蛇形单数列
当然,也可以使用gorm tag来根据业务需要自定义列名以及其他配置。
type User struct {
gorm.Model
Name string `gorm:"column:name;type:varchar(20);not null;uniqueIndex"`
Password string `gorm:"column:password;type:varchar(20);not null"`
}
定义表名
为gorm model定义表名.
这一步不是必须的,如果不为结构体定义表名,gorm会自动使用蛇形复数作为表名。
func (p Product) TableName() string{
return "user"
}
自动迁移模式
迁移模式是干啥的呢,就是说如果你这个数据库里没有这个表,会自动按照Model来创建表,如果有了表,会更新对应表的schema令其匹配Model。
不得不说,用gorm建表简直爽到,一句SQL都不用写,金柱老哥简直是天使。
db.AutoMigrate(&User{})
新建连接
config := Config{}
err := config.LoadFromJson("config.json")
if err != nil {
print("加载配置文件失败")
return
}
dsn := config.MySql.Username + ":" + config.MySql.Password + "@tcp(" +
config.MySql.Host + ":" + config.MySql.Port + ")/" + config.MySql.Database +
"?charset=" + config.MySql.Charset + "&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("连接数据库失败")
}
CRUD
创建数据
db.Create(&User{Name: "god", Password: "123456"})
除了创建单条数据以外,可以使用切片(这是切片吧,我忘了个求了,不管了)一次创建多条记录。
db.Create([]*User{
{Name: "cat", Password: "123456"},
{Name: "dog", Password: "123456"},
})
有时创建的记录会和已有数据有冲突,如果默认执行会报错,我们看一个场景,假设现在表中记录如下,其中name字段是唯一索引:
我们尝试create多条记录如下:
userList := []*User{
{Name: "cat", Password: "114514"},
{Name: "dog", Password: "1919810"},
{Name: "pig", Password: "8101919"},
}
如果直接create,因为cat和dog冲突的原因(在表里已经有同名用户),pig这个不冲突的记录也无法create。
但是如果用clause.OnConflict()处理数据冲突,就可以避免这种情况。
db.Clauses(clause.OnConflict{DoNothing: true}).Create(userList)
可以看到我们的pig同学并没有因为cat、dog这两个冲突的记录而被拒绝create。
查询数据
Fisrt查询一行
var user User
db.First(&user, 1)//查询主键为1的user
db.First(&user, "name=?", "god")//按条件查询
Find查询一堆
但是,使用db.First()有缺点:
- 只能返回一个数据
- 如果没查到符合条件的数据,会返回一个
ErrRecordNotFound
所以实际用的更多的是db.Find(),能够返回所有满足条件的数据,如果没有,就返回空数组。
res := db.Where("name LIKE ?", "%g%").Find(&users) //从上图中查询所有name含g的记录
println(res.RowsAffected) //查询结果数
println(res.Error) //查询是否有Error
for _, user := range users {
println(user.Name)
}
打印结果如下
2
(0x0,0x0)
dog
pig
注意,查询时默认不会返回deleted_at不为NULL的记录,所以这里并没有打印god。
Where该咋写
下面是一些常用的其他查询语句的用法
// in
db.Where("name IN ?", []string{"cat", "dog"}).Find(&users)
// like
res := db.Where("name LIKE ?", "%g%").Find(&users)
// and
db.Where("name Like ? AND age > ?", "%o%", "22").Find(&users)
使用结构体或map作为Where的参数
除了字符串以外,Where还支持传入结构体作为参数,但是结构体中的0值(0,false或其他零值)实际并不会写入SQL语句
db.Where(&User{Name: "dog", Age: 0}).Find(&users)
//实际生成的SQL语句是SELECT * FROM users WHERE name = "dog"
如果不想忽略0值,可以传入map作为参数。
db.Where(map[string]interface{}{
"name": "dog",
"age": 0,
}).Find(&users)
更新数据
// 更新单个字段
db.Model(&user).Update("password", "654321")
// 使用map更新多个字段
db.Model(&user).Updates(map[string]interface{}{"name": "dog", "password": "123456"})
删除数据
db.Delete(&user, "name = ?", "god")
如果定义的Model里没有gorm.DeletedAt字段,则会执行物理删除,否则执行软删除。
Where阶段是生成SQL的,Find、First、Update这些是执行的,执行过后,再追加SQL是无效的。
Gorm事务
使用db.Transcation自动rollback和commit
if err = db.Transaction(func(tx *gorm.DB) error {
if err = tx.Create(&User{Name: "name"}).Error; err != nil {
return err
}
if err = tx.Create(&User{Name: "name1"}).Error; err != nil {
tx.Rollback()
return err//返回error,自动rollback
}
return nil//返回nil,自动commit
}); err != nil {
return
}
Hook
gorm.io/zh_CN/docs/…
Hook 是在创建、查询、更新、删除等操作之前、之后自动调用的函数。
如果任何 Hook 返回错误,GORM将停止后续的操作并回滚事务。
gorm开启Hook时,在写数据时都会有一个默认事务,以保证Hook操作和本身的写操作在同一个事务中,但是这个默认事务会降低性能,如果不需要使用Hook,可以关闭默认事务来提高性能。钩子方法的函数签名应该是 func(*gorm.DB) error
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务