GORM——基础介绍与CRUD

一.介绍

1.概括

GORM的"全功能ORM"意味着它是一个功能完备、特性丰富的Go语言对象关系映射库,不仅实现了基础的CRUD操作,还提供了从关联关系、事务管理到自动迁移等全方位的数据库操作支持,让开发者能以面向对象的方式高效处理数据库交互。

2.特性,功能

  • 全功能 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
  • 可扩展且灵活的插件 API:数据库解析器(支持多数据库、读写分离)或 Prometheus 集成等
  • 每个特性都经过了测试的重重考验
  • 开发者友好

3.模型

GORM 通过将 Go 结构体(Go structs) 映射到数据库表来简化数据库交互。 了解如何在GORM中定义模型,是充分利用GORM全部功能的基础。

(1)模型定义

模型是使用普通结构体定义的。 这些结构体可以包含具有基本Go类型、指针或这些类型的别名,甚至是自定义类型(只需要实现 database/sql 包中的ScannerValuer接口)。

考虑以下 user 模型的示例:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { ID uint 主键的标准字段 Name string 普通字段 Email *string // A pointer to a string, allowing for null values Age uint8 // An unsigned 8-bit integer Birthday *time.Time // A pointer to time.Time, can be null MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields CreatedAt time.Time // Automatically managed by GORM for creation time UpdatedAt time.Time // Automatically managed by GORM for update time ignored string // fields that aren't exported are ignored } |

在此模型中:

  • 具体数字类型如 uintstringuint8 直接使用。
  • 指向 *string*time.Time 类型的指针表示可空字段。
  • 来自 database/sql 包的 sql.NullStringsql.NullTime 用于具有更多控制的可空字段。
  • CreatedAtUpdatedAt 是特殊字段,当记录被创建或更新时,GORM 会自动向内填充当前时间。
  • 非导出字段(以小字母开头)不映射

(2)约定

  1. 主键 :GORM 使用一个名为ID 的字段作为每个模型的默认主键。

  2. 表名 :默认情况下,GORM 将结构体名称转换为 snake_case(命名规范) 并为表名加上复数形式。 比如user结构体在表中就是users表, 并且GormUserName则表名gorm_user_names.

  3. 列名 :GORM 自动将结构体字段名称转换为 snake_case 作为数据库中的列名。

  4. 时间戳字段 :GORM使用字段 CreatedAtUpdatedAt 来自动跟踪记录的创建和更新时间。

遵循这些约定可以大大减少您需要编写的配置或代码量。 但是,GORM也具有灵活性,允许根据自己的需求自定义这些设置。

(3)gorm.Model

GORM提供了一个预定义的结构体,名为gorm.Model,其中包含常用字段:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // gorm.Model 的定义 type Model struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` } |

  • 将其嵌入在您的结构体中 : 您可以直接在您的结构体中嵌入 gorm.Model ,以便自动包含这些字段。 这对于在不同模型之间保持一致性并利用GORM内置的约定非常有用,嵌入结构详解

  • 包含的字段

    • ID :每个记录的唯一标识符(主键)。
    • CreatedAt :在创建记录时自动设置为当前时间。
    • UpdatedAt:每当记录更新时,自动更新为当前时间。
    • DeletedAt:用于软删除(将记录标记为已删除,而实际上并未从数据库中删除)。

(4)高级选项

字段级权限控制

可导出的字段在使用 GORM 进行 CRUD 时拥有全部的权限,此外,GORM 允许您用标签控制字段级别的权限。这样您就可以让一个字段的权限是只读、只写、只创建、只更新或者被忽略

注意: 使用 GORM Migrator 创建表时,不会创建被忽略的字段

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { Name string `gorm:"<-:create"` // 允许读和创建 Name string `gorm:"<-:update"` // 允许读和更新 Name string `gorm:"<-"` // 允许读和写(创建和更新) Name string `gorm:"<-:false"` // 允许读,禁止写 Name string `gorm:"->"` // 只读(除非有自定义配置,否则禁止写) Name string `gorm:"->;<-:create"` // 允许读和写 Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读) Name string `gorm:"-"` // 通过 struct 读写会忽略该字段 Name string `gorm:"-:all"` // 通过 struct 读写、迁移会忽略该字段 Name string `gorm:"-:migration"` // 通过 struct 迁移会忽略该字段 } |

创建/更新时间追踪(纳秒、毫秒、秒、Time)

GORM 约定使用 CreatedAtUpdatedAt 追踪创建/更新时间。如果您定义了这种字段,GORM 在创建、更新时会自动填充 当前时间

要使用不同名称的字段,您可以配置 autoCreateTimeautoUpdateTime 标签。

如果您想要保存 UNIX(毫/纳)秒时间戳,而不是 time,您只需简单地将 time.Time 修改为 int 即可

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充 UpdatedAt int // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充 Updated int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳纳秒数填充更新时间 Updated int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间 Created int64 `gorm:"autoCreateTime"` // 使用时间戳秒数填充创建时间 } |

嵌入结构体

对于匿名字段,GORM 会将其字段包含在父结构体中,例如:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type Author struct { Name string Email string } type Blog struct { Author ID int Upvotes int32 } // 等于 type Blog struct { ID int64 Name string Email string Upvotes int32 } |

对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,例如:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type Author struct { Name string Email string } type Blog struct { ID int Author Author `gorm:"embedded"` Upvotes int32 } // 等效于 type Blog struct { ID int64 Name string Email string Upvotes int32 } |

并且,您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀,例如:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type Blog struct { ID int Author Author `gorm:"embedded;embeddedPrefix:author_"` Upvotes int32 } // 等效于 type Blog struct { ID int64 AuthorName string AuthorEmail string Upvotes int32 } |

字段标签
  • Tags 是可选的:在声明模型时,tags 不是必须的,GORM 有默认行为
  • 不区分大小写columnCOLUMN 效果相同
  • 推荐使用 camelCase :虽然不区分大小写,但官方推荐使用 camelCase 格式(如 autoCreateTime 而不是 autocreatetime
  • 多个 tags 用分号分隔 :当一个字段需要多个配置时,使用分号 ; 分隔不同的 tags
标签名 说明
column 指定 db 列名
type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsize, autoIncrement... 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializer 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime
size 定义列数据类型的大小或长度,例如 size: 256
primaryKey 将列定义为主键
unique 将列定义为唯一键
default 定义列的默认值
precision 指定列的精度
scale 指定列大小
not null 指定列为 NOT NULL
autoIncrement 指定列为自动增长
autoIncrementIncrement 自动步长,控制连续记录之间的间隔
embedded 嵌套字段
embeddedPrefix 嵌入字段的列名前缀
autoCreateTime 创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime 创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
uniqueIndex index 相同,但创建的是唯一索引
check 创建检查约束,例如 check:age > 13,查看 约束 获取详情
<- 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
-> 设置字段读的权限,->:false 无读权限
- 忽略该字段,- 表示无读写,-:migration 表示无迁移权限,-:all 表示无读写迁移权限
comment 迁移时为字段添加注释

4.连接数据库

GORM 官方支持 MySQL、PostgreSQL、GaussDB、SQLite、SQL Server TiDB 和 Oracle 数据库

MySQL

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) func main() { 1. dsn := "user:pass@tcp(127.0.0.1:3306)/dbnamecharset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 2.根据配置文件来设置mysql参数 dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Database, ) // GORM 配置 gormConfig := &gorm.Config{ // 禁用外键约束 DisableForeignKeyConstraintWhenMigrating: true, // 跳过默认事务 SkipDefaultTransaction: true, } // 根据应用模式设置日志级别 if config.Get().App.Mode == "debug" { gormConfig.Logger = gormlogger.Default.LogMode(gormlogger.Info) } else { gormConfig.Logger = gormlogger.Default.LogMode(gormlogger.Silent) } // 连接数据库 db, err := gorm.Open(mysql.Open(dsn), gormConfig) } |

**注意:**想要正确的处理 time.Time ,您需要带上 parseTime 参数,要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4

MySQL 驱动程序提供了 一些高级配置 可以在初始化过程中使用,例如:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db, err := gorm.Open(mysql.New(mysql.Config{ DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name DefaultStringSize: 256, // string 类型字段的默认长度 DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 }), &gorm.Config{}) |

自定义驱动

GORM 允许通过 DriverName 选项自定义 MySQL 驱动,例如:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import ( _ "example.com/my_mysql_driver" "gorm.io/driver/mysql" "gorm.io/gorm" ) db, err := gorm.Open(mysql.New(mysql.Config{ DriverName: "my_mysql_driver", DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", }), &gorm.Config{}) |

现有的数据库连接(其他数据库连接也是类似的)

GORM 允许通过一个现有的数据库连接来初始化 *gorm.DB

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import ( "database/sql" "gorm.io/driver/mysql" "gorm.io/gorm" ) sqlDB, err := sql.Open("mysql", "mydb_dsn") gormDB, err := gorm.Open(mysql.New(mysql.Config{ Conn: sqlDB, }), &gorm.Config{}) |

二.CRUD接口

1.Create(新增数据)

简单插入单行

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} result := db.Create(&user) // 通过数据的指针来创建 user.ID // 返回插入数据的主键 result.Error // 返回 error result.RowsAffected // 返回插入记录的条数 |

我们还可以使用 Create() 创建多项记录:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| users := []*User{ {Name: "Jinzhu", Age: 18, Birthday: time.Now()}, {Name: "Jackson", Age: 19, Birthday: time.Now()}, } result := db.Create(users) // pass a slice to insert multiple row result.Error // returns error result.RowsAffected // returns inserted records count |

NOTE 你无法向 'create' 传递结构体,因此你应该传入数据的指针.

用指定的字段创建记录

创建记录并为指定字段赋值。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Select("Name", "Age", "CreatedAt").Create(&user) // INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775") |

创建记录并忽略传递给 'Omit' 的字段值

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Omit("Name", "Age", "CreatedAt").Create(&user) // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775") |

批量插入

要高效地插入大量记录,请将切片传递给Create方法。 GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法。 当这些记录可以被分割成多个批次时,GORM会开启一个事务来处理它们。

|-------------------------------------------------------------------------------------------------------------------------------------------------------|
| var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} db.Create(&users) for _, user := range users { user.ID // 1,2,3 } |

你可以通过db.CreateInBatches方法来指定批量插入的批次大小

|-------------------------------------------------------------------------------------------------------------------------------|
| var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}} // batch size 100 db.CreateInBatches(users, 100) |

UpsertCreate With Associations同样支持批量插入

注意 使用CreateBatchSize 选项初始化GORM实例,此后进行创建和关联操作时所有的INSERT行为都会遵循初始化时的配置。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ CreateBatchSize: 1000, }) db := db.Session(&gorm.Session{CreateBatchSize: 1000}) users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...} db.Create(&users) // INSERT INTO users xxx (5 batches) // INSERT INTO pets xxx (15 batches) |

根据 Map 创建时

GORM支持通过 map[string]interface{}[]map[string]interface{}{}来创建记录。

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Model(&User{}).Create(map[string]interface{}{ "Name": "jinzhu", "Age": 18, }) // batch insert from `[]map[string]interface{}{}` db.Model(&User{}).Create([]map[string]interface{}{ {"Name": "jinzhu_1", "Age": 18}, {"Name": "jinzhu_2", "Age": 20}, }) |

注意当使用map来创建时,钩子方法不会执行,关联不会被保存且不会回写主键。

默认值

你可以通过结构体Tag default来定义字段的默认值,示例如下:

|--------------------------------------------------------------------------------------------------------------|
| type User struct { ID int64 Name string `gorm:"default:galeone"` Age int64 `gorm:"default:18"` } |

这些默认值会被当作结构体字段的零值插入到数据库中

注意:

  • 当结构体的字段设置的默认值是零值的时候比如 0, '', false,这些字段值将不会被保存到数据库中,你可以使用指针类型或者Scanner/Valuer来避免这种情况。
  • 因为指针类型的零值是nil,gorm会识别到nil为空来获取默认值填充

|---------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { gorm.Model Name string Age *int `gorm:"default:18"` Active sql.NullBool `gorm:"default:true"` } |

注意 ,若要让字段在数据库中拥有默认值则必须使用defaultTag来为结构体字段设置默认值。如果想要在数据库迁移的时候跳过默认值,可以使用 default:(-),示例如下:

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { ID string `gorm:"default:uuid_generate_v3()"` // db func FirstName string LastName string Age uint8 FullName string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"` } |

2.Find(查询)

检索单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // 获取第一条记录(主键升序) 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) |

如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user)Find方法可以接受struct和slice的数据。
对单个对象使用无限制使用会查询整个表,只返回第一个非确定性且性能不佳的对象Find``db.Find(&user)

First and Last 方法会按主键排序找到第一条记录和最后一条记录 (分别)。 只有在目标 struct 是指针或者通过 db.Model() 指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。

例如:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| var user User var users []User // works because destination struct is passed in db.First(&user) // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1 // works because model is specified using `db.Model()` result := map[string]interface{}{} db.Model(&User{}).First(&result) // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1 // doesn't work result := map[string]interface{}{} db.Table("users").First(&result) // works with Take result := map[string]interface{}{} db.Table("users").Take(&result) // no primary key defined, results will be ordered by first field (i.e., `Code`) type Language struct { Code string Name string } db.First(&Language{}) // SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1 |

根据主键检索

如果主键是数字类型,可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免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); |

如果主键是字符串(例如像uuid),查询将被写成如下:

|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a") // SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a"; |

当目标对象有一个主键值时,将使用主键构建查询条件,例如:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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; |

NOTE: 如果您使用 gorm 的特定字段类型(例如 gorm.DeletedAt),它将运行不同的查询来检索对象。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { ID string `gorm:"primarykey;size:16"` Name string `gorm:"size:24"` DeletedAt gorm.DeletedAt `gorm:"index"` } var user = User{ID: 15} db.First(&user) // SELECT * FROM `users` WHERE `users`.`id` = '15' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1 |

检索全部对象

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // Get all records result := db.Find(&users) // SELECT * FROM users; result.RowsAffected // returns found records count, equals `len(users)` result.Error // returns error |

查询条件

String 条件

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // Get first matched record db.Where("name = ?", "jinzhu").First(&user) // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; // Get all matched records 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'; |

如果对象设置了主键,条件查询将不会覆盖主键的值,而是用 And 连接条件。 例如:

|----------------------------------------------------------------------------------------------------------------------------------------------|
| var user = User{ID: 10} db.Where("id = ?", 20).First(&user) // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1 |

这个查询将会给出record not found错误 所以,在你想要使用例如 user 这样的变量从数据库中获取新值前,需要将例如 id 这样的主键设置为nil。

Struct & Map 条件

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // Struct db.Where(&User{Name: "jinzhu", Age: 20}).First(&user) // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1; // Map db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users) // SELECT * FROM users WHERE name = "jinzhu" AND age = 20; // Slice of primary keys db.Where([]int64{20, 21, 22}).Find(&users) // SELECT * FROM users WHERE id IN (20, 21, 22); |

注释 使用struct查询时,GORM 只会查询非零字段,这意味着如果你字段的值是0 ,'',false 或其他零值,就不会用于构建查询条件,例如:

|------------------------------------------------------------------------------------------------------------|
| db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users) // SELECT * FROM users WHERE name = "jinzhu"; |

要在查询条件中包含零值,可以使用映射,将所有键值作为查询条件,例如:

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

指定结构体查询字段

使用struct搜索时,可以通过将相关字段名或数据库名传递到,指定在查询条件中使用的具体结构体值,例如:Where()

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

内联条件

查询条件可以内嵌在类似的方法中,且方式与 First,``Find,``Where类似

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // Get by primary key if it were a non-integer type db.First(&user, "id = ?", "string_primary_key") // SELECT * FROM users WHERE id = 'string_primary_key'; // Plain SQL db.Find(&user, "name = ?", "jinzhu") // SELECT * FROM users WHERE name = "jinzhu"; db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20) // SELECT * FROM users WHERE name <> "jinzhu" AND age > 20; // Struct db.Find(&users, User{Age: 20}) // SELECT * FROM users WHERE age = 20; // Map db.Find(&users, map[string]interface{}{"age": 20}) // SELECT * FROM users WHERE age = 20; |

Not 条件

和where使用方法类似

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Not("name = ?", "jinzhu").First(&user) // SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1; // Not In db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users) // SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2"); // Struct db.Not(User{Name: "jinzhu", Age: 18}).First(&user) // SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1; // Not In slice of primary keys db.Not([]int64{1,2,3}).First(&user) // SELECT * FROM users WHERE id NOT IN (1,2,3) 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'; // Struct db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18); // Map db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18); |

选择特定字段

Select允许你指定想从数据库中检索的字段。否则,GORM 会默认选择所有字段。

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Select("name", "age").Find(&users) // SELECT name, age FROM users; db.Select([]string{"name", "age"}).Find(&users) // SELECT name, age FROM users; db.Table("users").Select("COALESCE(age,?)", 42).Rows() // SELECT COALESCE(age,'42') FROM users |

排序

指定从数据库检索记录时的排序方式

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Order("age desc, name").Find(&users) // SELECT * FROM users ORDER BY age desc, name; // Multiple orders db.Order("age desc").Order("name").Find(&users) // SELECT * FROM users ORDER BY age desc, name; db.Clauses(clause.OrderBy{ Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true}, }).Find(&User{}) // SELECT * FROM users ORDER BY FIELD(id,1,2,3) |

Limit & Offset

Limit指定要检索的最大记录数,Offset指定在开始返回记录前要跳过的记录数量

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Limit(3).Find(&users) // SELECT * FROM users LIMIT 3; // Cancel limit condition with -1 db.Limit(10).Find(&users1).Limit(-1).Find(&users2) // SELECT * FROM users LIMIT 10; (users1) // SELECT * FROM users; (users2) db.Offset(3).Find(&users) // SELECT * FROM users OFFSET 3; db.Limit(10).Offset(5).Find(&users) // SELECT * FROM users OFFSET 5 LIMIT 10; // Cancel offset condition with -1 db.Offset(10).Find(&users1).Offset(-1).Find(&users2) // SELECT * FROM users OFFSET 10; (users1) // SELECT * FROM users; (users2) |

Group By & Having

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type result struct { Date time.Time Total int } db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result) // SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1 db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result) // SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group" rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows() defer rows.Close() for rows.Next() { ... } rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows() defer rows.Close() for rows.Next() { ... } type Result struct { Date time.Time Total int64 } db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results) |

Distinct

去重

|---------------------------------------------------------------------------|
| db.Distinct("name", "age").Order("name, age desc").Find(&results) |

Distinct 也可以配合 Pluck, Count 使用

Joins

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type result struct { Name string Email string } 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 rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows() for rows.Next() { ... } db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results) // multiple joins with parameter db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user) |

Joins 预加载

您可以使用 Joins 实现单条 SQL 预加载关联记录,例如:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Joins("Company").Find(&users) // SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`; // inner join db.InnerJoins("Company").Find(&users) // SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` INNER JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`; |

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users) // SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true; |

Joins 一个衍生表

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { Id int Age int } type Order struct { UserId int FinishedAt *time.Time } query := db.Table("order").Select("MAX(order.finished_at) as latest").Joins("left join user user on order.user_id = user.id").Where("user.age > ?", 18).Group("order.user_id") db.Model(&Order{}).Joins("join (?) q on order.finished_at = q.latest", query).Scan(&results) // SELECT `order`.`user_id`,`order`.`finished_at` FROM `order` join (SELECT MAX(order.finished_at) as latest FROM `order` left join user user on order.user_id = user.id WHERE user.age > 18 GROUP BY `order`.`user_id`) q on order.finished_at = q.latest |

Scan

Scan 结果至 struct,用法与 Find 类似

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type Result struct { Name string Age int } var result Result db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result) // Raw SQL db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result) |

3.更新

保存所有字段

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; |

Save是一个upsert函数:

  • 如果该值不包含主键,则执行Create
  • 如果该值有主键,首先执行更新 (所有字段,通过 )。Select(*)
  • 如果更新后 ,它会自动退回到。rows affected = 0``Create

💡 注意 :保证会有更新或插入。
为防止无行匹配时的意外创建,请使用 Select(*)。Updates()Save

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Save(&User{Name: "jinzhu", Age: 100}) // INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00") db.Save(&User{ID: 1, Name: "jinzhu", Age: 100}) // UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1 |

NOTE 不要将 和 一同使用, 这是 未定义的行为Save``Model
注意 为避免歧义和并发问题,该方法被有意从 Generics API 中移除。请改用 or 方法。Save``Create``Updates

更新单个列

当使用 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; |

更新多列

Updates 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // 根据 `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 更新时, GORM 将只更新非零值字段。 你可能想用 map 来更新属性,或者使用 Select 声明字段来更新

更新选定字段

如果您想要在更新时选择、忽略某些字段,您可以使用 SelectOmit

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // 选择 Map 的字段 // User 的 ID 是 `111`: db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET name='hello' 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; // 选择 Struct 的字段(会选中零值的字段) db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0}) // UPDATE users SET name='new_name', age=0 WHERE id=111; // 选择所有字段(选择包括零值字段的所有字段) db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0}) // 选择除 Role 外的所有字段(包括零值字段的所有字段) db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0}) |

更新 Hook

GORM 支持的 hook 包括:BeforeSave, BeforeUpdate, AfterSave, AfterUpdate. 更新记录时将调用这些方法,查看 Hooks 获取详细信息

|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| func (u *User) BeforeUpdate(tx *gorm.DB) (err error) { if u.Role == "admin" { return errors.New("admin user not allowed to update") } return } |

批量更新

如果没有通过 Model 指定一个含有主键的记录,GORM 会执行批量更新

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // Update with struct db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18}) // UPDATE users SET name='hello', age=18 WHERE role = 'admin'; // Update with map 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); |

阻止全局更新

如果你执行一个没有任何条件的批量更新,GORM 默认不会运行,并且会返回 ErrMissingWhereClause 错误

你可以用一些条件,使用原生 SQL 或者启用 AllowGlobalUpdate 模式,例如:

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu") // UPDATE users SET `name` = "jinzhu" WHERE 1=1 db.Exec("UPDATE users SET name = ?", "jinzhu") // UPDATE users SET name = "jinzhu" db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu") // UPDATE users SET `name` = "jinzhu" |

更新的记录数

Get the number of rows affected by a update

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // Get updated records count with `RowsAffected` result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18}) // UPDATE users SET name='hello', age=18 WHERE role = 'admin'; result.RowsAffected // returns updated records count result.Error // returns updating error |

4.删除

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // Email 的 ID 是 `10` db.Delete(&email) // DELETE from emails where id = 10; // 带额外条件的删除 db.Where("name = ?", "jinzhu").Delete(&email) // DELETE from emails where id = 10 AND name = "jinzhu"; |

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串------译者注)。查看 查询-内联条件(Query Inline Conditions) 了解详情。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Delete(&User{}, 10) // DELETE FROM users WHERE id = 10; db.Delete(&User{}, "10") // DELETE FROM users WHERE id = 10; db.Delete(&users, []int{1,2,3}) // DELETE FROM users WHERE id IN (1,2,3); |

钩子函数

对于删除操作,GORM 支持 BeforeDeleteAfterDelete Hook,在删除记录时会调用这些方法,查看 Hook 获取详情

|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| func (u *User) BeforeDelete(tx *gorm.DB) (err error) { if u.Role == "admin" { return errors.New("admin user not allowed to delete") } return } |

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{}) // DELETE from emails where email LIKE "%jinzhu%"; db.Delete(&Email{}, "email LIKE ?", "%jinzhu%") // DELETE from emails where email LIKE "%jinzhu%"; |

可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| var users = []User{{ID: 1}, {ID: 2}, {ID: 3}} db.Delete(&users) // DELETE FROM users WHERE id IN (1,2,3); db.Delete(&users, "name LIKE ?", "%jinzhu%") // DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3); |

阻止全局删除

当你试图执行不带任何条件的批量删除时,GORM将不会运行并返回ErrMissingWhereClause 错误

如果一定要这么做,你必须添加一些条件,或者使用原生SQL,或者开启AllowGlobalUpdate 模式,如下例:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| db.Delete(&User{}).Error // gorm.ErrMissingWhereClause db.Delete(&[]User{{Name: "jinzhu1"}, {Name: "jinzhu2"}}).Error // gorm.ErrMissingWhereClause db.Where("1 = 1").Delete(&User{}) // DELETE FROM `users` WHERE 1=1 db.Exec("DELETE FROM users") // DELETE FROM users db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{}) // DELETE FROM users |

返回删除行的数据

返回被删除的数据,仅当数据库支持回写功能时才能正常运行,如下例:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // 回写所有的列 var users []User DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users) // DELETE FROM `users` WHERE role = "admin" RETURNING * // users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}} // 回写指定的列 DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users) // DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary` // users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}} |

软删除

如果你的模型包含了 gorm.DeletedAt字段(该字段也被包含在gorm.Model中),那么该模型将会自动获得软删除的能力!

当调用Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时间,而后的一般查询方法将无法查找到此条记录。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| // user's ID is `111` db.Delete(&user) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111; // Batch Delete db.Where("age = ?", 20).Delete(&User{}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20; // Soft deleted records will be ignored when querying db.Where("age = 20").Find(&user) // SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL; |

如果你并不想嵌套gorm.Model,你也可以像下方例子那样开启软删除特性:

|--------------------------------------------------------------------------------|
| type User struct { ID int Deleted gorm.DeletedAt Name string } |

查找被软删除的记录

你可以使用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.Model使用*time.Time作为DeletedAt 的字段类型,不过软删除插件gorm.io/plugin/soft_delete同时也提供其他的数据格式支持

提示 当使用DeletedAt创建唯一复合索引时,你必须使用其他的数据类型,例如通过gorm.io/plugin/soft_delete插件将字段类型定义为unix时间戳等等

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import "gorm.io/plugin/soft_delete" type User struct { ID uint Name string `gorm:"uniqueIndex:udx_name"` DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"` } |

Unix 时间戳

使用unix时间戳作为删除标志

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import "gorm.io/plugin/soft_delete" type User struct { ID uint Name string DeletedAt soft_delete.DeletedAt } // 查询 SELECT * FROM users WHERE deleted_at = 0; // 软删除 UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1; |

你同样可以指定使用毫秒 milli或纳秒 nano作为值,如下例:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { ID uint Name string DeletedAt soft_delete.DeletedAt `gorm:"softDelete:milli"` // DeletedAt soft_delete.DeletedAt `gorm:"softDelete:nano"` } // 查询 SELECT * FROM users WHERE deleted_at = 0; // 软删除 UPDATE users SET deleted_at = /* current unix milli second or nano second */ WHERE ID = 1; |

使用 1 / 0 作为 删除标志

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import "gorm.io/plugin/soft_delete" type User struct { ID uint Name string IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"` } // 查询 SELECT * FROM users WHERE is_del = 0; // 软删除 UPDATE users SET is_del = 1 WHERE ID = 1; |

混合模式

混合模式可以使用 01或者unix时间戳来标记数据是否被软删除,并同时可以保存被删除时间

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| type User struct { ID uint Name string DeletedAt time.Time IsDel soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"` // use `1` `0` // IsDel soft_delete.DeletedAt `gorm:"softDelete:,DeletedAtField:DeletedAt"` // use `unix second` // IsDel soft_delete.DeletedAt `gorm:"softDelete:nano,DeletedAtField:DeletedAt"` // use `unix nano second` } // 查询 SELECT * FROM users WHERE is_del = 0; // 软删除 UPDATE users SET is_del = 1, deleted_at = /* current unix second */ WHERE ID = 1; |

相关推荐
金融小白数据分析之路1 小时前
java 打包exe maven 版本
java·开发语言·maven
changshuaihua0012 小时前
useState 状态管理
开发语言·前端·javascript·react.js
聆风吟º2 小时前
【Python编程日志】Python入门基础(二):行 | 缩进 | print输出
开发语言·python·print··缩进
lsx2024062 小时前
Servlet 点击计数器
开发语言
Undoom2 小时前
告别“复读机”:深度拆解星云 SDK 如何破解数字人实时交互的“不可能三角”
后端
卷心菜狗2 小时前
Python进阶-闭包与装饰器
开发语言·python·学习
weixin_408099672 小时前
Lua请求文字识别ocr api
图像处理·人工智能·后端·ocr·lua·api·文字识别
凯瑟琳.奥古斯特2 小时前
C++变量命名进阶技巧
开发语言·c++
不羁的fang少年2 小时前
Netty网络模型
java·开发语言