这是我参与「第五届青训营」伴学笔记创作活动的第 3 天
gorm
gorm是一款使用golang进行编写的orm框架,使用orm框架可以将关系型数据库中的数据写入go的对应结构体中进行封装
要使用gorm,首先通过go get -u gorm.io/gorm拉取相关依赖
连接数据库
根据所需数据库选择对应的驱动
import "gorm.io/driver/mysql"
import "gorm.io/driver/postgres"
import "gorm.io/driver/sqlite"
import "gorm.io/driver/mssql"
连接mysql代码如下
func main() {
db,err:=gorm.Open(
mysql.Open("name:password@(host:port)/db?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true,
})
}
GORM Model
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner和Valuer,如:
type Test struct {
ID int `gorm:primaryKey`
Name string
Deleted gorm.DeletedAt
}
ORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间
或者也可以实现TableName()方法来指定gorm model对应的表名
func (t Test) TableName() string {
return "t_test"
}
CRUD
插入
//单条插入
db.Create(t)
//批量插入
testMulti := &[]Test{{Name: "test1"}, {Name: "test2"}, {Name: "test3"}}
db.Create(testMulti)
插入成功后,gorm会将插入结果反写入传入Create()方法的对象中
如果插入时未设置主键,在插入成功后可获取其的主键值
testMulti := &[]Test{{Name: "test1"}, {Name: "test2"}, {Name: "test3"}}
db.Create(testMulti)
for _, user := range *testMulti {
fmt.Println(user.ID)
}
使用 CreateInBatches 创建时,你还可以指定创建的数量
testBantches := &[]Test{{Name: "testB1"}, {Name: "testB2"}, {Name: "testB3"}}
db.CreateInBatches(testBantches, 3)
也根据map插入
db.Model(&Test{}).Create([]map[string]interface{}{
{"Name": "test map1"},
{"Name": "test map2"},
})
可以通过标签 default 为字段定义默认值,如:
type User struct {
ID int64
Name string `gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
}
插入记录到数据库时,默认值会被用于 填充值为 零值的字段
查询
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
在使用find时,查询不懂数据则不会返回错误
// 获取第一条记录(主键升序)
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
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
First, Last方法将按主键排序查找第一/最后一条记录,只有在用struct查询或提供model value时才有效,如果当前model没有定义主键,将按第一个字段排序
var user User
// 可以
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 可以
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 不行
result := map[string]interface{}{}
db.Table("users").First(&result)
// 但可以配合 Take 使用
result := map[string]interface{}{}
db.Table("users").Take(&result)
// 根据第一个字段排序
type Language struct {
Code string
Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
注意:
- 当使用结构作为条件查询时,GORM只会查询非零值字段。这意味着如果您的字段值为0、
''、false 或其他零值, 该字段不会被用于构建查询条件,可以使用Map或者Select()方法来构建查询条件。
检索全部对象
// 获取全部记录
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // 返回找到的记录数,相当于 `len(users)`
result.Error // returns error
用主键检索
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);
检索全部对象
// 获取全部记录
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // 返回找到的记录数,相当于 `len(users)`
result.Error // returns error
条件查询(where
// 获取第一条匹配的记录
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY 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';
更新
Save 会保存所有的字段,即使字段是零值
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 。当使用了 Model 方法,且该对象主键有值,该值会被用于构建的条件
// 条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 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=111 AND active=true;
// 根据 `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` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
使用Struct更新时,只会更新非零值,如果需要更新零值可以使用Map更新或使用Select选择字段。
删除
如果您的模型包含了一个 gorm.DeletedAt 字段,它将自动获得软删除的能力!
Deleted gorm.DeletedAt
拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 正常的查询gorm并不会把该记录展示在结果中
// 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 = 20 AND deleted_at IS NULL;
当然可以使用 Unscoped 找到被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
也可以使用 Unscoped 永久删除匹配的记录
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
性能优化
对于写操作(创建、更新、删除), 为了确保数据的完整性,GORM 会将它们封装在事务内运行。但这会降低性能,你可以使用SkipDefaultTransaction关闭默认事务。 使用PrepareStmt缓存预编译语句可以提高后续调用的速度,本机测试提高大约35 %左右。
db,err:=gorm.Open(
mysql.Open("name:password@(host:port)/db?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true,
})
总结
在之前使用Java语言的时候用过mybatis,前者使用习惯上都是基于MySQL语句,在操作层面把SQL语句拼写完成。但是在gorm框架中几乎看不到完整的SQL语句,都是通过gorm自定义的方法对sql语句进行了拆解,或许在代码编写时不是那么灵活,但在sql安全性方面多了一层保障,并且不用开发人员自己去维护sql语句的映射关系
引用
该文章部分内容来自于以下课程或网页:
- 字节内部课:Go 语言入门 - 工程实践
- GORM教程