GORM CRUD | 青训营

61 阅读5分钟

0 GORM的特性

  • 全功能 ORM
  • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
  • Create,Save,Update,Delete,Find 中钩子方法
  • 支持 PreloadJoins 的预加载
  • 事务,嵌套事务,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…
  • 每个特性都经过了测试的重重考验
  • 开发者友好

1 GORM CRUD

1.1 创建数据

1.1.1 创建数据、判断表是否为空

GORM简单示例,以及使用NewRecord()查询主键是否存在

dsn是数据源名称:

  • DSN 前缀:【mysql:】等
  • host:主机上的数据库服务器
  • port:主机上数据库服务器监听的端口号。
  • dbname:数据库的名称。
  • charset:字符集设置
package main 
​
import(
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)
​
type Product struct{
    ID uint `gorm:"primarykey"`
    Code string `gorm:"column:code"`
    Price uint `gorm:"column:user_id"`
}
​
func main(){
    db, err:= gorm.Open(
        mysql.Open(dsn:"username:password@tcp(localhost:9910)/database?charset=utf8"),
        &gorm.Config{}
    )
    if err != nil: "failed to connect database"
    
    //other
}

1.1.2 默认值

无论是未赋值还是赋值为零值,所有字段的零值, 比如0, "",false或者其它零值,都不会保存到数据库内,会使用默认值。

//如何使用默认值?通过使用 default 标签为字段定义默认值
type User struct{
    ID int64
    Name string `gorm:"default:galeone"`
    Age int64 `gorm:"default:18"` 
}

如果想保存零值,可以使用指针或实现 Scanner/Valuer接口:

使用指针方式实现零值存入数据库

//使用指针
type User struct {
  ID   int64
  Name *string `gorm:"default:'小王子'"`
  Age  int64
}
user := User{Name: new(string), Age: 18))}
db.Create(&user)  //此时数据库中该条记录name字段的值就是''

使用Scanner/Valuer接口方式实现零值存入数据库

//使用Scanner/Valuer
type User struct {
    ID int64
    Name sql.NullString `gorm:"default:'小王子'"` //sql.NullString 实现了Scanner/Valuer接口
    Age  int64
}
user := User{Name: sql.NullString{"", true}, Age:18}
db.Create(&user)  //此时数据库中该条记录name字段的值就是''

如何使用 Upsert?使用clause.OnConflict处理数据冲突

//以不处理冲突为例,创建一条数据
p := &Product{Code:"D42", ID:1}
db.Clauses(clause.OnConflict{DoNothing:true}).Create(&p)

1.2 查询数据

First()获取第一条记录(主键升序),查询不到数据则返回 ErrRecordNotFound。

Find()可用于查询多条数据,条件查询使用where,可进行模糊查询等操作。

u := &User{}
db.First(u) //SELECT * FROM users ORDER BY id LIMIT 1;

//查询多条数据
users := make([]*User, 0)
result := db.Where("age > 10").Find(&users) //SELECT * FROM users where age > 10;
fmt.Println(result.RowsAffected) //返回找到的记录数,相当于`Len(users)`
fmt.Println(result.Error) //returns error

//IN SELECT * FROM users WHERE name IN ('jinzhu', 'jinzhu 2');
db.Where("name IN?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
//LIKE SELECT * FROM users WHERE name LIKE '%jin%';
db.Where("name LIKE?","%jin%").Find(&users)
//AND SELECT*FROM users WHERE name = 'jinzhu' AND age >= 22;
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)

//SELECT * FROM users WHERE name = "jinzhu";
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
//SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)

First 的使用踩坑

  • 使用 First 时,需要注意查询不到数据会返回 ErrRecordNotFound。

  • 使用 Find 查询多条数据,查询不到数据不会返回错误。

使用结构体作为查询条件

当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0、""、false 或其他零值,该字段不会被用于构建查询条件,使用 Map 来构建查询条件。

1.3 更新数据

更新所有字段

Save()默认会更新该对象的所有字段,即使你没有赋值。

db.First(&user)
​
user.Name = "七米"
user.Age = 99
db.Save(&user)
​
////  UPDATE `users` SET `created_at` = '2020-02-16 12:52:20', `updated_at` = '2020-02-16 12:54:55', `deleted_at` = NULL, `name` = '七米', `age` = 99, `active` = true  WHERE `users`.`deleted_at` IS NULL AND `users`.`id` = 1

更新修改字段

如果你只希望更新指定字段,可以使用Update或者UpdatesUpdate用于单属性更新,Updates用于多属性更新。特殊的,当使用 struct 更新时,GORM只会更新那些非零值的字段。

// 更新单个属性,如果它有变化
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
​
// 根据给定的条件更新单个属性
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;
​
// 使用 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 更新多个属性,只会更新其中有变化且为非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
​
// 对于下面的操作,不会发生任何更新,"", 0, false 都是其类型的零值
db.Model(&user).Updates(User{Name: "", Age: 0, Active: false})

更新选定字段

如果你想更新或忽略某些字段,你可以使用 SelectOmit。第一条示例在map中设置了三个字段的新值,但使用Select后只更新name字段。第二条示例在map中设置了三个字段的新值,使用Omit()忽略了name字段新值的设置。注:先使用Omit()Select再使用Updates()

db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
​
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
//// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

批量更新

批量更新时Hooks(钩子函数)不会运行。

db.Table("users").Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
//// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
​
// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18;
​
// 使用 `RowsAffected` 获取更新记录总数
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected

1.4 删除数据

删除记录

删除记录时,要确保主键字段有值,GORM 会通过主键去删除记录,如果主键为空,GORM 会删除该 model 的所有记录。

// 删除现有记录
db.Delete(&email)
//// DELETE from emails where id=10;

涓流虽寡,浸成江河;爝火虽微,卒能燎野。