文章目录
Gorm 基本使用
Gorm 是一个已经迭代了 10 多年的功能强大的 ORM 框架,被广泛使用并目拥有非常丰富的开源扩展。
Gorm 的安装
使用 go get 命令在项目中安装 gorm,其中数据库驱动根据实际情况安装,我这里安装的是 MySQL
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
Gorm 快速入门
下面是 Gorm 进行单表操作的简单用法,因为 golang 是强类型语言,需要用 model 来映射数据库表,可以通过 Gorm 的 AutoMigrate 函数生成数据库,也可以根据数据库建表语句反向生成 model,在线工具网址 http://sql2struct.atotoa.com/
- 创建 model
- 为 model 设置表名
- 利用 dsn 连接到数据库,获取连接
- 对数据库表进行操作
// 定义 gorm model
type Product struct {
Code string
Price uint
}
// 为 model 设置表名
func (p Product) TableName() string {
return "product"
}
func main() {
dsn := "username:root@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Printf("mysql connect error %v", err)
}
// 迁移 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 创建数据
- 创建数据时处理冲突问题,使用 clause.OnConflict 处理冲突
p := &Product{Code:"D42",ID:1}
db.Clauses(clause.OnConflict{DoNothing:true}).Create(&p)
- 创建数据时使用默认值
type User struct {
ID int64
Name string `gorm:"default:user"`
Age int64 `gorm:default:18`
}
Gorm 查询数据
- 检索单个对象
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
如果你想避免 ErrRecordNotFound 错误,你可以使用 Find,比如 db.Limit(1).Find(&user),Find 方法可以接受 struct 和 slice 的数据。
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
- 根据主键检索
如果主键是数字,则可以使用内联条件使用主键检索对象。使用字符串时需要格外小心,避免SQL 注入
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
当目标对象有一个主键值时,将使用主键构建查询条件
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;
var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;
- 根据条件检索
- String 条件
// Get first matched record
db.Where("name = ?", "user").First(&user)
// SELECT * FROM users WHERE name = 'user' ORDER BY id LIMIT 1;
- Map 条件
// Map
db.Where(map[string]interface{}{"name": "user", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "user" AND age = 20;
- Not 条件,用法类似 where 条件
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
- Or 条件
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
- 排序条件
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
- Limit 和 offset 条件
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
- Join 条件
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
Gorm 更新数据
- 保存所有字段,Save 会保存所有的字段,即使字段是零值
db.Save(&user)
- 更新单个列
// User's ID is `111`:
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
- 更新多个列
使用 Struct 更新时,只会更新非零值,如果需要更新零值可以使用 Map 更新或使用 Select 选择字段:
// Update attributes with `struct`, will only update non-zero fields
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
Gorm 删除
- 删除一条记录
删除一条记录时,删除对象需要指定主键,否则会触发 批量删除
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
- 批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
Gorm 原生 SQL
原生 SQL
db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)
Gorm 事务
Gorm提供了Begin、Commit、Rollback方法用于使用事务
tx := db.begin()
// 这里执行对数据库的操作,应该用 tx 而不是 db
// 遇到错误时执行 tx.Rollback() 回滚
tx.Commit()
Gorm提供了Tansaction方法用于自动提交事务,避免用户漏写Commit、Rollbcak.
db.Transaction(func(tx *gorm.DB) {
// 这里执行操作逻辑
} )
Gorm Hook
GORM在提供了CURD的Hook能力。
Hook是在创建、查询、更新、删除等操作之前、之后自动调用的函数。
如果任何Hook返回错误,GORM将停止后续的操作并回滚事务。
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// 业务代码
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
// 业务代码
}
Gorm 性能提高
- 对于写操作(创建、更新、删除),为了确保数据的完整性,GORM会将它们封装在事务内运行。但这会降低性能,你可以使用 SkipDefaultTransaction 关闭默认事务。
- 使用 PrepareStmt 缓存预编译语句可以提高后续调用的速度
db, err := gorm.Open(dsn, &gorm.Config{
SkipDefaultTransaction: true, // 关闭默认事务
PrepareStmt:true // 缓存预编译语句
})
Gorm 生态
gorm 拥有非常丰富的扩展生态,以下是一部分常用生态
GORM 代码生成工具 | https://github.com/go-gorm/gen |
---|---|
GORM 分片库方案 | https://github.com/go-gorm/sharding |
GORM 手动索引 | https://github.com/go-gorm/hints |
GORM 乐观锁 | https://github.com/go-gorm/optimisticlock |
GORM 读写分离 | https://github.com/go-gorm/dbresolver |
GORM OpenTelemetry 扩展 | https://github.com/go-gorm/opentelemetry |