Go语言使用gorm操作数据库
介绍
在用 Go 开发项目时,我们免不了要和数据库打交道。目前,GitHub 上 star 数最多的是 GORM,它也是当前 Go 项目中使用最多的 ORM。
GORM 是 Go 语言的 ORM 包,功能强大,调用方便。像腾讯、华为、阿里这样的大厂,都在使用 GORM 来构建企业级的应用。GORM 有很多特性,开发中常用的核心特性如下:功能全。使用 ORM 操作数据库的接口,GORM 都有,可以满足我们开发中对数据库调用的各类需求。支持钩子方法。这些钩子方法可以应用在 Create、Save、Update、Delete、Find 方法中。开发者友好,调用方便。支持 Auto Migration。支持关联查询。支持多种关系数据库,例如 MySQL、Postgres、SQLite、SQLServer 等。
在企业级 Go 项目开发中,使用 GORM 库主要用来完成以下数据库操作:
连接和关闭数据库。连接数据库时,可能需要设置一些参数,比如最大连接数、最大空闲连接数、最大连接时长等。
插入表记录。可以插入一条记录,也可以批量插入记录。
更新表记录。可以更新某一个字段,也可以更新多个字段。
查看表记录。可以查看某一条记录,也可以查看符合条件的记录列表。
删除表记录。可以删除某一个记录,也可以批量删除。删除还支持永久删除和软删除。
在一些小型项目中,还会用到 GORM 的表结构自动迁移功能。
安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
不同的数据库安装不同的驱动即可。
连接
设置连接地址信息
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "",
SingularTable: true, //使用单数表名,启用该选项后,`User`表将是 `user`
NameReplacer: nil,
NoLowerCase: false,
},
})
如果需要 GORM 正确地处理 类型,在连接数据库时需要带上 parseTime 参数。如果要支持完整的 UTF-8 编码,可将charset=utf8更改为charset=utf8mb4。
GORM 支持连接池,底层是用 database/sql 包来维护连接池的,连接池设置如下:
sqlDB, err := db.DB()
sqlDB.SetMaxOpenConns(100) //最大连接数
sqlDB.SetMaxIdleConns(100) //最大空闲连接数
sqlDB.SetConnMaxLifetime(10 * time.Second) //空闲连接最多存活时间
自动迁移表结构
type User struct {
gorm.Model
Name string `gorm:"column: name"`
Age int `gorm: "column: age"`
Birthday time.Time `gorm:"column: birthday"`
}
//实现TableName方法可以修改表名
func (u *User) TableName() string {
return "user"
}
//自动迁移
db.AutoMigrate(&User{})
操作
创建记录
user := User{
Name: "kuludi",
Age: 20,
Birthday: time.Now(),
}
result := db.Create(&user) //通过数据的指针来创建
函数会返回如下 3 个值:
:返回插入数据的主键,这个是直接赋值给 user 变量。
:返回 error。
:返回插入记录的条数。
当需要插入的数据量比较大时,可以批量插入,以提高插入性能:
var users = []User{{Name: "red1"}, {Name: "red2"}, {Name: "red3"}}
db.Create(&users)
for _, user := range users {
fmt.Println(user.ID, user.Name) // 1 red1 2 red2 3 red3
}
删除记录
我们可以通过 Delete 方法删除记录:
//delete from user where name = "red1"
db.Where("name = ?", "red1").Delete(&User{})
GORM 也支持根据主键进行删除,例如:
//delete from user where id = 1;
db.Delete(&User{}, 1)
不过,我更喜欢使用 的方式进行删除,这种方式有两个优点。
第一个优点是删除方式更通用。使用 不仅可以根据主键删除,还能够随意组合条件进行删除。
第二个优点是删除方式更显式,这意味着更易读。如果使用(&User{}, 10),你还需要确认 User 的主键,如果记错了主键,还可能会引入 Bug。
此外,GORM 也支持批量删除:
db.Where("name in (?)", []string{"red2", "red3"}).Delete(&User{})
两种删除方式
-
软删除
软删除是指执行 Delete 时,记录不会被从数据库中真正删除。GORM 会将 DeletedAt 设置为当前时间,并且不能通过正常的方式查询到该记录。如果模型包含了一个 字段,GORM 在执行删除操作时,会软删除该记录。上面的删除方法就是一个软删除。
可以看到,GORM 并没有真正把记录从数据库删除掉,而是只更新了 deleted_at 字段。在查询时,GORM 查询条件中新增了AND deleted_at IS NULL条件,所以这些被设置过 deleted_at 字段的记录不会被查询到。对于一些比较重要的数据,我们可以通过软删除的方式删除记录,软删除可以使这些重要的数据后期能够被恢复,并且便于以后的排障。
我们可以通过下面的方式查找被软删除的记录:
db.Unscoped().Where("name = ?", "red1").Find(&User{})
-
永久删除
如果想永久删除一条记录,可以使用 Unscoped:
db.Unscoped().Delete(&User{})
更新记录
GORM 中,最常用的更新方法如下:
db.First(&user)
user.Name = "hu"
user.Age = 300
db.Save(&user)
上述方法会保留所有字段,所以执行 Save 时,需要先执行 First,获取某个记录的所有列的值,然后再对需要更新的字段设置值。
还可以指定更新单个列:
db.Model(&User{}).Where("name = ?","hu").Update("age",500)
也可以指定更新多个列:
db.Model(&User{}).Where("name = ?", "hu").Updates(User{Name: "hong", Age: 888, Birthday: time.Now()})
这里要注意,这个方法只会更新非零值的字段。
查询记录
GORM 支持不同的查询方法,下面我来讲解三种在开发中经常用到的查询方式,分别是检索单个记录、查询所有符合条件的记录和智能选择字段。
- 检索单个记录
下面是检索单个记录的示例代码:
// 获取第一条记录(主键升序)
// SELECT * FROM users ORDER BY id LIMIT 1;
db.First(&user)
// 获取最后一条记录(主键降序)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
db.Last(&user)
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
如果 model 类型没有定义主键,则按第一个字段排序。
- 查询所有符合条件的记录
示例代码如下:
users := make([]*User, 0)
// SELECT * FROM users WHERE name <> 'red';
db.Where("name <> ?", "red").Find(&users)
- 智能选择字段
你可以通过 Select 方法,选择特定的字段。我们可以定义一个较小的结构体来接受选定的字段:
type APIUser struct {
ID uint
Name string
}
// SELECT `id`, `name` FROM `users` LIMIT 10;
db.Model(&User{}).Limit(10).Find(&APIUser{})
除了上面讲的三种常用的基本查询方法,GORM 还支持高级查询,下面我来介绍下。
高级查询
GORM 支持很多高级查询功能,这里我主要介绍 4 种。
- 指定检索记录时的排序方式
示例代码如下:
// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc, name").Find(&users)
- Limit & Offset
Offset 指定从第几条记录开始查询,
Limit 指定返回的最大记录数。Offset 和 Limit 值为 -1 时,消除 Offset 和 Limit 条件。
另外,Limit 和 Offset 位置不同,效果也不同。
// SELECT * FROM users OFFSET 5 LIMIT 10;
db.Limit(10).Offset(5).Find(&users)
- Distinct
Distinct 可以从数据库记录中选择不同的值。
db.Distinct("name", "age").Order("name, age desc").Find(&results)
- Count
Count 可以获取匹配的条数。
var count int64
// SELECT count(1) FROM users WHERE name = 'red'; (count)
db.Model(&User{}).Where("name = ?", "red").Count(&count)
GORM 还支持很多高级查询功能,比如内联条件、Not 条件、Or 条件、Group & Having、Joins、Group、FirstOrInit、FirstOrCreate、迭代、FindInBatches 等。