这是我参与「第五届青训营 」笔记创作活动的第 14 天
前言
前两篇笔记分别介绍了 Golang 微服务 HTTP 框架 Hertz 和 Golang 微服务 RPC 框架 Kitex,本文将要介绍面向golang语言的一种ORM(持久层)框架 GORM。
重点内容
GORM 简介
GORM 快速入门
GORM CRUD 接口介绍
知识点介绍
GORM 简介
GORM 是面向 Golang 语言的一种 ORM(持久层)框架,支持多种数据库的接入,例如 MySQL,PostgreSQL,SQLite,SQL Server,Clickhouse。此框架的特点,弱化了开发者对于 SQL 语言的掌握程度,使用提供的 API 进行底层数据库的访问。
特性:
全功能 ORM
关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
Create,Save,Update,Delete,Find 中钩子方法
支持 Preload、Joins 的预加载
事务,嵌套事务,Save Point,Rollback To Saved Point
Context,预编译模式,DryRun 模式
批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
复合主键,索引,约束
Auto Migration
自定义 Logger
灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
每个特性都经过了测试的重重考验
开发者友好
GORM 快速入门
安装
GORM 支持多种数据库的接入,这里以 MySQL 为例:
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
快速入门
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
type Product struct {
Code string
Price uint
}
func(p Product) TableName() string {
return"product"// 返回的字符串即表名
}
funcmain() {
// 连接数据库(需要传递一个 dsn)
db, err := gorm.Open(
mysql.Open(dsn:"user:pass@(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 创建数据 Create() 方法支持创建一条(传递对象)和多条数据(传递数组)
db.Create(&Product{Code: "D42", Price: 100})
// 查询数据 先声明一个结构体, 然后使用 First() 方法(注意是传递指针)
var product Product
db.First(&product, 1) // 根据整型主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
// 更新数据 - 用 Update() 方法将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// 更新数据 - 用 Updates() 方法更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"}) // 传递 map 可以更新零值
// 删除数据
db.Delete(&product, 1)
}
GORM 支持的数据库
GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server
连接 SQLServer 数据库为例:
import (
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
// github.com/denisenkom/go-mssqldb
dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})
GORM 通过驱动来连接数据库,如果需要连接其他类型的数据库,可以复用或自行开发驱动。
GORM CRUD 接口介绍
创建
创建一条记录
p := &Product{Code: "D42"}
res := db.Create(p) // 因为 gorm 是链式调用, 使用 Creat() 后会返回一个gorm对象
fmt.Println(res.Error) // 获取 err
fmt.Println(p.ID) // 返回插入数据的主键
创建多条记录
products := []*Product{{Code: "D41"}, {Code: "D42"}, {Code: "D43"}}
res = db.Create(products) // 批量创建数据
fmt.Println(res.Error) // 获取 err
for _, p := range products {
fmt.Println(p.ID)
}
可以通过标签 default 为字段定义默认值
type User struct {
ID int64
Name string`gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
uuid.UUID UUID `gorm:"type:uuid;default:gen_random_uuid()"`// 数据库函数
}
GORM 为不同数据库提供了兼容的 Upsert 支持
// 不处理冲突
DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)
查询
查询单个对象:GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
// 获取第一条记录(主键升序)
db.First(&user)
//SELECT*FROM users ORDERBY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
//SELECT*FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
//SELECT*FROM users ORDERBY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error //returns error
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
根据主键检索
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);
条件查询
String 条件
// 获取第一条匹配的记录
db.Where("name = ?", "jinzhu").First(&user)
//SELECT*FROM users WHERE name ='jinzhu'ORDERBY id LIMIT 1;
// 获取全部匹配的记录
db.Where("name <> ?", "jinzhu").Find(&users)
//SELECT*FROM users WHERE name <>'jinzhu';
//IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
//SELECT*FROM users WHERE name IN ('jinzhu','jinzhu 2');
//LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
//SELECT*FROM users WHERE name LIKE'%jin%';
//AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
//SELECT*FROM users WHERE name ='jinzhu'AND age >=22;
//Time
db.Where("updated_at > ?", lastWeek).Find(&users)
//SELECT*FROM users WHERE updated_at >'2000-01-01 00:00:00';
//BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
//SELECT*FROM users WHERE created_at BETWEEN'2000-01-01 00:00:00'AND'2000-01-08 00:00:00';
Struct & Map 条件:注意 当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0、''、false 或其他零值,该字段不会被用于构建查询条件
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu"AND age = 20ORDERBY id LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu"AND age = 20;
// 主键切片条件
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
子查询
db.Where("amount > ?", db.Table("orders").Select("AVG(amount)")).Find(&orders)
//SELECT*FROM "orders" WHERE amount > (SELECTAVG(amount) FROM "orders");
subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
//SELECTAVG(age) as avgage FROM `users` GROUPBY `name` HAVINGAVG(age) > (SELECTAVG(age) FROM `users` WHERE name LIKE "name%
更新
更新单个列
// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
//UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10'WHERE id=111AND active=true;
更新多个列
根据 struct 更新属性
// 根据 `struct` 更新属性,只会更新非零值的字段
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;
根据 map更新属性
// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10'WHERE id=111;
SQL 表达式更新
// product 的 ID 是 `3`
DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
//UPDATE "products" SET "price" = price *2+100, "updated_at" ='2013-11-17 21:34:10'WHERE "id" =3;
DB.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
//UPDATE "products" SET "price" = price *2+100, "updated_at" ='2013-11-17 21:34:10'WHERE "id" =3;
DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//UPDATE "products" SET "quantity" = quantity -1WHERE "id" =3;
DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//UPDATE "products" SET "quantity" = quantity -1WHERE "id" =3AND quantity >1;
删除
物理删除
db.Delete(&User{}, 10)
//DELETEFROM users WHERE id =10;
db.Delete(&User{}, "10")
//DELETEFROM users WHERE id =10;
db.Delete(&users, []int{1,2,3})
//DELETEFROM users WHERE id IN (1,2,3);
软删除:如果模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),将自动获得软删除的能力!拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
//user 的 ID 是 `111`
db.Delete(&user)
//UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id =111;
// 批量删除
db.Where("age = ?", 20).Delete(&User{})
//UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age =20;
// 在查询时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
//SELECT*FROM users WHERE age =20AND deleted_at ISNULL;
可以使用 Unscoped 找到被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
事务
GORM 提供了 Begin、Commit、Rollback 方法用于使用事务
tx := db.Begin() // 开始事务if err := tx.Error; err != nil {
return err
}
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback() // 遇到错误时回滚事务return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
tx.Commit()
GORM 提供了 Tansaction 方法用于自动提交事务,避免用户漏写Commit、 Rollback
db.Transaction(func(tx *gorm.DB)error {
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 返回任何错误都会回滚事务return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}
// 返回 nil 提交事务returnnil
})
Hook
Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。
如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。
钩子方法的函数签名应该是 func(*gorm.DB) error
代码示例:
func(u *User) BeforeCreate(tx *gorm.DB)(err error) {
u.UUID = uuid.New()if !u.IsValid() {
err = errors.New("can't save invalid data")
}
return
}
func(u *User) AfterCreate(tx *gorm.DB)(err error) {
ifu.ID == 1 {
tx.Model(u).Update("role", "admin")
}
return
}
总结
本文主要介绍了 GORM 框架的相关基础知识,介绍了常用的接口函数,具体的细节还是需要仔细研究官方文档。这三个框架的学习都可以看出官方文档的重要性,同时学习框架还是要多上手练习才行。