Gorm基础使用【青训营笔记】

179 阅读4分钟

本文是青训营课程-后端入门-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字段是唯一索引:
image.png
我们尝试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)

image.png
可以看到我们的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
// 提交或回滚事务