GORM 是 Go 语言中一款性能很好的 ORM 库,对开发人员也相对比较友好,能够显著提升开发效率。
GORM 有很多特性,常用的核心特性如下:
功能全。使用 ORM 操作数据库的接口,GORM 都有,可以满足开发中对数据库调用的各类需求。
支持钩子方法。这些钩子方法可以应用在 Create、Save、Update、Delete、Find 方法中。
支持 Auto Migration 。
支持关联查询。
支持多种关系数据库,例如 MySQL、Postgres、SQLite、SQLServer 等。
GORM 库主要用来完成以下数据库操作:
连接和关闭数据库。连接数据库时,可能需要设置一些参数,比如最大连接数、最大空闲连接数、最大连接时长等。
插入表记录。可以插入一条记录,也可以批量插入记录。
更新表记录。可以更新某一个字段,也可以更新多个字段。
查看表记录。可以查看某一条记录,也可以查看符合条件的记录列表。
删除表记录。可以删除某一个记录,也可以批量删除。删除还支持永久删除和软删除。
在 Linux 系统中直接打开命令行终端,安装 GORM 输入如下命令:
go get -u github.com/jinzhu/gorm
以 SQLite 数据库为例,输入如下命令:
go get -u gorm.io/driver/sqlite
以 SQLite 数据库为例,编写如下程序:
package main
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
db, err := gorm.Open(sqlite.Open("/home/Tao/test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 迁移 schema
db.AutoMigrate(&Product{})
// Create
db.Create(&Product{Code: "D42", Price: 100})
// Read
var product Product
db.First(&product, 1) // 根据整型主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
// Update - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
// Delete - 删除 product
db.Delete(&product, 1)
}
GORM 使用模型(Models)来映射一个数据库表,默认情况下,使用 ID 作为主键,使用结构体名的 snake_cases 作为表名,使用字段名的 snake_case 作为列名,并使用 CreatedAt
、UpdatedAt
、DeletedAt
字段追踪创建、更新和删除时间。
使用 GORM 的默认规则可以减少代码量,此外也可以直接指明字段名和表名,例如以下模型:
type Animal struct {
AnimalID int64 // 列名 `animal_id`
Birthday time.Time // 列名 `birthday`
Age int64 // 列名 `age`
}
上述模型对应的表名为 animals ,列名分别为 animal_id 、 birthday 和 age ,可以通过以下方式来重命名表名和列名,并将 AnimalID 设置为表的主键:
type Animal struct {
AnimalID int64 `gorm:"column:animalID;primarykey"` // 将列名设为 `animalID`
Birthday time.Time `gorm:"column:birthday"` // 将列名设为 `birthday`
Age int64 `gorm:"column:age"` // 将列名设为 `age`
}
func (a *Animal) TableName() string {
return "animal"
}
上面的代码中,通过使用 primaryKey 标签指定主键,通过使用 column 标签指定列名,通过给 Models 添加 TableName 方法指定表名。
数据库表通常会包含 4 个字段,具体如下所示:
ID
:自增字段,也作为主键。
CreatedAt
:记录创建时间。
UpdatedAt
:记录更新时间。
DeletedAt
:记录删除时间(软删除时有用)。
GORM 预定义了包含这 4 个字段的 Models ,在定义自己的 Models 时,可以直接内嵌到结构体内,例如:
type Animal struct {
gorm.Model
AnimalID int64 `gorm:"column:animalID"` // 将列名设为 `animalID`
Birthday time.Time `gorm:"column:birthday"` // 将列名设为 `birthday`
Age int64 `gorm:"column:age"` // 将列名设为 `age`
}
Models 中的字段能支持很多 GORM 标签,但如果不使用 GORM 自动创建表和迁移表结构的功能,很多标签是用不到的。
在进行数据库的 CURD 操作之前,首先需要连接数据库。以连接 MySQL 数据库为例,可以通过以下程序代码的方式实现 :
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
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{})
}
如果需要 GORM 正确地处理 time.Time 类型,在连接数据库时需要带上 parseTime 参数;如果要支持完整的 UTF-8 编码,可将charset=utf8 更改为 charset=utf8mb4 。
GORM 支持连接池,底层是用 database/sql
包来维护连接池的,连接池设置如下:
sqlDB, err := db.DB()
sqlDB.SetMaxIdleConns(100) // 设置 MySQL 的最大空闲连接数(推荐100)
sqlDB.SetMaxOpenConns(100) // 设置 MySQL 的最大连接数(推荐100)
sqlDB.SetConnMaxLifetime(time.Hour) // 设置 MySQL 的空闲连接最大存活时间(推荐10s)
创建数据记录可以通过 db.Create()
方法,例如如下的程序代码:
type User struct {
gorm.Model
Name string
Age uint8
Birthday *time.Time
}
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
db.Create()
方法会返回如下 3 个值:
user.ID
:返回插入数据的主键,这个是直接赋值给 user 变量。
result.Error
:返回 error 。
result.RowsAffected
:返回插入记录的条数。
当需要插入的数据量比较大时,可以批量插入,例如如下的程序代码:
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
DB.Create(&users)
for _, user := range users {
user.ID // 1,2,3
}
可以通过 Delete 方法删除记录,例如如下的程序代码:
// DELETE from users where id = 10 AND name = "jinzhu";
db.Where("name = ?", "jinzhu").Delete(&user)
GORM 也支持根据主键进行删除,例如如下的程序代码:
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, 10)
使用 db.Where 的方式进行删除有两个优点。
(1)第一个优点是删除方式更通用。使用 db.Where 不仅可以根据主键删除,还能够随意组合条件进行删除。
(2)第二个优点是删除方式更显式,这意味着更易读。如果使用db.Delete(&User{}, 10),你还需要确认User的主键,如果记错了主键,还可能会引入Bug。
此外,GORM 也支持批量删除,例如如下的程序代码:
db.Where("name in (?)", []string{"jinzhu", "colin"}).Delete(&User{})
GORM 支持两种删除方法:软删除和永久删除。
软删除是指执行 Delete 时,记录不会被从数据库中真正删除。GORM会将 DeletedAt 设置为当前时间,并且不能通过正常的方式查询到该记录。如果模型包含了一个 gorm.DeletedAt 字段,GORM 在执行删除操作时,会软删除该记录。
下面的删除方法就是一个软删除:
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
db.Where("age = ?", 20).Delete(&User{})
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
db.Where("age = 20").Find(&user)
可以看到,GORM 并没有真正把记录从数据库删除掉,而是只更新了 deleted_at 字段。在查询时,GORM 查询条件中新增了 AND deleted_at IS NULL 条件,所以这些被设置过 deleted_at 字段的记录不会被查询到。
对于一些比较重要的数据,可以通过软删除的方式删除记录,软删除可以使这些重要的数据后期能够被恢复,并且便于以后的排障。
可以通过下面的方式查找被软删除的记录:
// SELECT * FROM users WHERE age = 20;
db.Unscoped().Where("age = 20").Find(&users)
如果想永久删除一条记录,可以使用 Unscoped ,例如如下的程序代码:
// DELETE FROM orders WHERE id=10;
db.Unscoped().Delete(&order)
或者也可以在模型中去掉 gorm.DeletedAt 。
在 GORM 中,最常用的更新方法,例如如下的程序代码:
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
db.Save(&user)
上述方法会保留所有字段,所以执行 Save 时,需要先执行 First ,获取某个记录的所有列的值,然后再对需要更新的字段设置值。
还可以指定更新单个列,例如如下的程序代码:
// UPDATE users SET age=200, updated_at='2013-11-17 21:34:10' WHERE name='colin';
db.Model(&User{}).Where("name = ?", "colin").Update("age", 200)
也可以指定更新多个列,例如如下的程序代码:
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE name = 'colin';
db.Model(&user).Where("name", "colin").Updates(User{Name: "hello", Age: 18, Active: false})
这里要注意,这个方法只会更新非零值的字段。
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 错误 如果model类型没有定义主键,则按第一个字段排序。
errors.Is(result.Error, gorm.ErrRecordNotFound)
例如如下的程序代码:
users := make([]*User, 0)
// SELECT * FROM users WHERE name <> 'jinzhu';
db.Where("name <> ?", "jinzhu").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 支持很多高级查询功能,这里主要介绍 4 种。
例如如下的程序代码:
// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc, name").Find(&users)
Offset 指定从第几条记录开始查询,Limit指定返回的最大记录数。Offset和Limit值为-1时,消除Offset和Limit条件。另外,Limit和Offset位置不同,效果也不同。
例如如下的程序代码:
// SELECT * FROM users OFFSET 5 LIMIT 10;
db.Limit(10).Offset(5).Find(&users)
Distinct 可以从数据库记录中选择不同的值。
例如如下的程序代码:
db.Distinct("name", "age").Order("name, age desc").Find(&results)
Count 可以获取匹配的条数。
例如如下的程序代码:
var count int64
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
GORM 还支持很多高级查询功能(如内联条件、Not 条件、Or 条件、Group & Having、Joins、Group、FirstOrInit、FirstOrCreate、迭代、FindInBatches 等)。
GORM 支持原生查询 SQL 和执行 SQL ,原生查询 SQL 用法如下:
type Result struct {
ID int
Name string
Age int
}
var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
原生执行 SQL 用法,例如如下的程序代码:
db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1,2,3})
GORM 支持钩子功能,例如下面这个在插入记录前执行的钩子:
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if u.Name == "admin" {
return errors.New("invalid name")
}
return
}
GORM 中调用 db.Error()
方法就能获取到错误信息,例如如下的程序代码:
var GormUser = new(GormUser)
err:= db.Model (6GormUser).Where("phone =?", "18988888888").
Update("phone","13888888888").Error
if err!=nil {
// 错误处理
}
GORM 中事务的处理也很简单,用 db.Begin()
方法声明开启事务,结束的时候调用 tx.Commit()
方法,异常的时候调用 tx.Rollback()
方法回滚,例如如下的程序代码:
//开启事务
tx:= db.Begin()
GormUser := GormUser(
Phone:"18988888888",
Name: "Shirdon",
Password: md5Password("666666"),//用户密码
if err := tx.Create(&GormUser).Error; err != nil {
//事务回滚
tx.Rollback()
fmt.Println(err)
db.First(6GormUser, "phone -?", "18988888888")
//事务提交
tx.Commit()
}