GO的orm框架gorm| 青训营笔记

194 阅读6分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 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 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间

或者也可以实现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 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 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教程