Gorm基础使用(使用Gorm实现增删改查)

GORM(全称:Go Object Relational Mapping)是一个用于Go编程语言的对象关系映射(ORM)库。ORM是一种编程技术,用于在关系型数据库和面向对象编程语言之间建立映射,使得数据库中的数据可以以对象的方式进行操作,从而简化数据库操作和数据持久化的过程。

GORM的主要目标是简化Go语言开发者与数据库之间的交互,提供一种优雅的方式来执行常见的数据库操作,如创建、查询、更新和删除数据等。使用GORM,开发者可以通过定义Go结构体来映射数据库表,然后可以使用GORM提供的API来执行数据库操作,而无需编写繁琐的SQL语句。

快速入门

安装Gorm:

bash 复制代码
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

以下是Gorm框架的快速入门代码案例:

  • 定义了Product数据模型,其中gorm.Model包含了一些默认字段: IDCreatedAtUpdatedAtDeletedAt,用于处理数据的时间戳和软删除。
  • 创建了一个 SQLite 数据库连接实例 db,并指定了数据库文件为 test.db
  • AutoMigrate 方法用于自动创建或更新数据库表结构,这里将 Product 结构体传递进去,确保数据库中存在与之对应的表。
  • 对数据库表进行基础增删改查演示
go 复制代码
package main

import (
  "gorm.io/gorm"
  "gorm.io/driver/sqlite"
)

type Product struct {
  gorm.Model
  Code  string
  Price uint
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&Product{})

  // Create
  db.Create(&Product{Code: "D42", Price: 100})

  // Read
  var product Product
  db.First(&product, 1) // 根据整型主键查找
  db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录

  // Update - 将 product 的 price 更新为 200
  db.Model(&product).Update("Price", 200)
  // Update - 更新多个字段
  db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
  db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})

  // Delete - 删除 product
  db.Delete(&product, 1)
}

声明模型

模型定义

模型是标准的 struct,由 Go 的基本数据类型、实现了 ScannerValuer 接口的自定义类型及其指针或别名组成

例如:

go 复制代码
type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

约定

GORM 倾向于约定优于配置 默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间

gorm.Model

GORM 定义一个 gorm.Model 结构体,其包括字段 IDCreatedAtUpdatedAtDeletedAt

go 复制代码
// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

可以将它嵌入到您的结构体中,以包含这几个字段

Gorm标签

GORM 标签(Tag),用于在定义数据模型(结构体)时标识主键字段。在 GORM 中,通过在结构体字段上使用标签来指定数据库表的字段属性、关系和约束等信息。

以下是一些常用的 GORM 标签及其用途:

  1. gorm:"column:column_name" 用于指定数据库表中对应的列名。
  2. gorm:"primaryKey" 标识字段为主键。
  3. gorm:"autoIncrement" 标识字段为自增(通常与主键一起使用)。
  4. gorm:"unique" 标识字段的值在表中必须唯一。
  5. gorm:"index" 在字段上创建索引。
  6. gorm:"not null" 标识字段不能为空。
  7. gorm:"size:n" 指定字段的大小或长度。
  8. gorm:"default:default_value" 指定字段的默认值。
  9. gorm:"comment:comment_text" 添加注释或说明。
  10. gorm:"type:data_type" 指定数据库列的数据类型。
  11. gorm:"uniqueIndex:index_name" 在字段上创建带有名称的唯一索引。
  12. gorm:"embedded" 标识嵌入结构体,将其字段合并到当前结构体。
  13. gorm:"polymorphic" 多态关联,用于多态关联关系的标识。
  14. gorm:"associationForeignKey:foreign_key" 指定关联关系的外键。
  15. gorm:"association_autoupdate:false" 关联关系更新时不自动更新关联对象。
  16. gorm:"association_autocreate:false" 关联关系更新时不自动创建关联对象。
  17. gorm:"index:index_name;unique" 在字段上创建带有名称的唯一索引。
  18. gorm:"foreignKey:column_name" 指定外键列名。
  19. gorm:"constraint:constraint_name" 指定约束名称。
  20. gorm:"many2many:join_table" 定义多对多关系。

连接数据库

GORM 官方支持的数据库类型有:MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB,按如下步骤连接:

  1. 安装了 GORM 和对应的数据库驱动,在Go代码中导入必要的库,如:

    go 复制代码
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    )
  2. 使用 gorm.Open() 方法来建立数据库连接,以下为MySQL为例

    • usernamepasswordhostportdbname处分别填写MySQL数据库的用户名密码服务器IP地址端口数据库名

      DSN(Data Source Name)是一种用于描述数据库连接信息的字符串。它包含了一系列参数,用于指定数据库的位置、访问权限、连接选项等信息,以便应用程序能够与数据库建立连接。DSN 在连接数据库时起到了重要的作用,可以理解为连接数据库的地址和凭据的配置。

    • 通过gorm.Open()建立数据库连接,两个参数分别为数据库驱动对象Gorm配置体

    go 复制代码
    func main() {
        dsn := "username:password@tcp(host:port)/dbname"
        db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }
        defer db.Close()
    
        // 使用 db 进行数据库操作
    }

**注意:**想要正确的处理 time.Time ,需要带上 parseTime 参数,要支持完整的 UTF-8 编码,需要将 charset=utf8 更改为 charset=utf8mb4,即:dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"

Gorm配置

在 GORM 中,&gorm.Config{} 是用于配置 GORM 行为的一个结构体。源码如下:

go 复制代码
type Config struct {
  SkipDefaultTransaction   bool
  NamingStrategy           schema.Namer
  Logger                   logger.Interface
  NowFunc                  func() time.Time
  DryRun                   bool
  PrepareStmt              bool
  DisableNestedTransaction bool
  AllowGlobalUpdate        bool
  DisableAutomaticPing     bool
  DisableForeignKeyConstraintWhenMigrating bool
}

通过配置对象,你可以设置各种选项来控制 GORM 在执行数据库操作时的行为。以下是一些常见的配置选项示例:

go 复制代码
import (
    "gorm.io/gorm"
)

func main() {
    // 创建数据库连接
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        // 配置选项...
    })
    if err != nil {
        panic("failed to connect database")
    }
    defer db.Close()

    // 使用 db 进行数据库操作
}

在上述代码中,你可以在 &gorm.Config{} 中添加一些配置选项。以下是一些常见的配置选项及其说明:

  • DisableForeignKeyConstraintWhenMigrating: 在迁移数据库表结构时,禁用外键约束。默认为 false

  • PrepareStmt: 预编译 SQL 语句,可以提高性能。默认为 true

  • SkipDefaultTransaction: 在查询和 CRUD 操作中,不使用默认的事务。默认为 false

  • NamingStrategy: 自定义数据库表和字段命名策略。例如:

    go 复制代码
    NamingStrategy: schema.NamingStrategy{
      SingularTable: true, // 使用单数表名
    },
  • Logger: 自定义日志记录器,用于记录数据库操作日志。

  • DryRun: 开启干运行模式,用于生成 SQL 语句而不执行。默认为 false

  • FullSaveAssociations: 在保存数据时,也保存关联的数据。默认为 false

这只是一些常见的配置选项示例,实际上 GORM 提供了更多的选项,可以根据需要进行设置。通过在 &gorm.Config{} 中配置这些选项,你可以控制 GORM 在数据库操作中的行为和性能。

CURD接口

创建

创建记录

go 复制代码
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通过数据的指针来创建

user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

Create()还可以插入多条记录:

go 复制代码
users := []*User{
    User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
    User{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

指定字段创建

  1. 指定创建某几个字段

    go 复制代码
    db.Select("Name", "Age", "CreatedAt").Create(&user)

    INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

  2. 指定忽略某几个字段

    go 复制代码
    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")

批量插入

通过CreateInBatches函数插入,传入一个切片slice

go 复制代码
var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}
// batch size 100
db.CreateInBatches(users, 100)

也可以通过for循环遍历每一条数据进行插入,CreateInBatches函数插入时仅会开启一个事务,批量插入的数据只会整体成功提交或者全部回滚,可根据需要选择批量插入的方式

钩子

GORM 提供了钩子(Hooks)机制,允许你在执行数据库操作的不同阶段插入自定义的逻辑。这些钩子可以用于在创建、更新、删除等数据库操作之前或之后执行一些额外的操作。你可以使用 GORM 的钩子来实现一些业务逻辑、数据校验、日志记录等功能。

以下是一些常用的 GORM 钩子方法:

  1. BeforeCreate 在创建数据记录之前调用。
  2. AfterCreate 在创建数据记录之后调用。
  3. BeforeUpdate 在更新数据记录之前调用。
  4. AfterUpdate 在更新数据记录之后调用。
  5. BeforeDelete 在删除数据记录之前调用。
  6. AfterDelete 在删除数据记录之后调用。

可用于设置数据库表中创建时间、更新时间等场景

查询

检索单个对象

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

go 复制代码
// 获取第一条记录(主键升序)
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而不带limit,db.Find(&user)将会查询整个表并且只返回第一个对象,这是性能不高并且不确定的。

FirstLast 方法会按照主键(primary key)的顺序查找第一条和最后一条记录,分别返回结果。这两个方法只有在将目标结构体的指针作为参数传递给方法,或者通过 db.Model() 指定模型时才有效。此外,如果相关模型未定义主键,那么模型将按照第一个字段的顺序排序。例如:

go 复制代码
var user User
var users []User

// 有效,因为传递了目标结构体的指针
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// 有效,因为通过 `db.Model()` 指定了模型
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)

// 未定义主键,结果将按照第一个字段(即 `Code`)排序
type Language struct {
  Code string
  Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

以上示例中,First 方法和 Last 方法在查询结果中按照主键的顺序找到第一条和最后一条记录。对于 db.Table("users").First(&result) 这种情况,由于没有指定模型,无法确定排序方式,所以该查询无效。而使用 db.Table("users").Take(&result) 方法可以返回任意一条记录。对于未定义主键的情况,查询结果将按照第一个字段的顺序排序。

根据主键查询

通过使用内联条件可以根据主键检索对象,如果主键是一个数字。但是当使用字符串作为主键时,需要额外小心,以避免 SQL 注入攻击

go 复制代码
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),查询将如下所示:

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

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

go 复制代码
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;

注意:如果你使用 GORM 的特定字段类型,如 gorm.DeletedAt,它将会运行不同的查询来检索对象。

go 复制代码
ype 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

查询所有数据

go 复制代码
// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

条件查询

String 条件

go 复制代码
// 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"条件使用。例如:

go 复制代码
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

这个查询会导致记录未找到的错误。因此,在你想要使用类似 user 这样的变量从数据库获取新值之前,应该将主键属性(比如 id)设置为 nil

Struct 和 Map条件

  1. 使用结构体(Struct)条件: 使用一个结构体对象作为查询条件,GORM 会根据结构体中非零字段的值构建查询条件。示例代码如下:

    go 复制代码
    db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
  2. 使用映射(Map)条件: 使用一个映射作为查询条件,其中的键值对将被用作查询条件。示例代码如下:

    go 复制代码
    db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
  3. 使用主键切片条件: 使用主键的切片作为查询条件,GORM 会生成 IN 子句。示例代码如下:

    go 复制代码
    db.Where([]int64{20, 21, 22}).Find(&users)
    // SELECT * FROM users WHERE id IN (20, 21, 22);

需要注意的是,当使用结构体作为查询条件时,GORM 只会构建非零字段的查询条件。如果字段的值为 0、''、false 或其他零值,它将不会被用于构建查询条件。例如:

go 复制代码
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";

如果希望包含零值作为查询条件,可以使用映射(Map)来实现,其中所有的键值对都会被用作查询条件,无论其值是零还是非零。例如:

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

指定结构体字段查询

在使用结构体进行查询时,你可以通过在 Where() 方法中传入相关字段名或数据库字段名来指定在查询条件中使用结构体的哪些特定值,示例如下:

go 复制代码
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;

上述示例中,通过在 Where() 方法中传入 "name""Age",你可以指定在查询条件中使用结构体 UserNameAge 字段值。这将构建相应的查询条件并从数据库中检索数据。

age = 0是因为Go语言中struct结构体中,数字类型变量不赋值默认为0

内联条件

查询条件可以内联到类似 FirstFind 这样的方法中,类似于 Where 方法的用法。

go 复制代码
// 如果主键是非整数类型,可以使用内联条件按主键获取记录
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';

// 普通 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;

// 结构体
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;

// 映射
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;

Not条件

构建 NOT 条件,与 Where 类似的方式使用。

go 复制代码
// NOT 条件
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");

// 结构体
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 切片的主键
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

Or条件

go 复制代码
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 会选择所有字段。

go 复制代码
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;

排序

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

go 复制代码
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// 多个排序条件
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

使用 LimitOffset 方法来指定从数据库中检索记录的数量和偏移量。

go 复制代码
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

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

// 使用 -1 来取消偏移条件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)

Group By & Having

go 复制代码
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)

Count

Count 用于获取匹配的记录数

go 复制代码
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;

// Count with Distinct
db.Model(&User{}).Distinct("name").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count(distinct(name)) FROM deleted_users

// Count with Group
users := []User{
  {Name: "name1"},
  {Name: "name2"},
  {Name: "name3"},
  {Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
count // => 3

更新

Save

Save 会保存所有的字段,即使字段是零值

go 复制代码
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 方法是一个综合性的函数。如果保存的值不包含主键,它会执行创建(Create)操作,否则会执行更新(Update)操作(包括所有字段)。

go 复制代码
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

注意:不要将 Save 与模型一起使用,这是一个未定义行为。

更新单个列

在使用 Update 方法更新单个列时,需要提供条件,否则会引发错误 ErrMissingWhereClause。条件可以通过 Where 方法指定。如果使用 Model 方法并且该模型具有主键值,那么主键将用于构建条件。

以下是一些示例:

使用条件进行更新:

go 复制代码
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;

使用模型的主键值进行更新:

go 复制代码
// 假设 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;

同时使用条件和模型的主键值进行更新:

go 复制代码
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 AN

更新多列

Updates 方法支持使用结构体或 map[string]interface{} 进行更新。使用结构体进行更新时,默认情况下只会更新非零值的字段。

示例:

使用结构体进行属性更新,只会更新非零值字段:

go 复制代码
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 进行属性更新:

go 复制代码
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;

注意,在使用结构体进行更新时,GORM 只会更新非零值的字段。如果需要更新所有字段,可以使用 map 进行更新,或者使用 Select 方法指定要更新的字段。此外,你也可以使用 Select 方法来指定要更新的字段。

更新选定字段

如果想要在更新时选择特定字段,或者忽略某些字段,可以使用 SelectOmit 方法。

示例:

使用 Select 方法选择字段进行更新:

go 复制代码
// 假设 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;

使用 Omit 方法忽略字段进行更新:

go 复制代码
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;

使用 Select 方法选择字段进行更新,包括零值字段:

go 复制代码
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;

使用 Select 方法选择所有字段进行更新,包括零值字段:

go 复制代码
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

使用 Omit 方法选择所有字段进行更新,但忽略指定的字段:

go 复制代码
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

在这些示例中,使用 Select 方法可以选择要更新的字段,而使用 Omit 方法可以忽略某些字段。这使得你可以更加精确地控制更新操作。

删除

删除一条记录

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

go 复制代码
// 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 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串)

go 复制代码
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 会执行批量删除,它将删除所有匹配的记录

go 复制代码
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 方法,以便更高效的删除数据量大的记录

go 复制代码
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); 
相关推荐
Find25 天前
MaxKB 集成langchain + Vue + PostgreSQL 的 本地大模型+本地知识库 构建私有大模型 | MarsCode AI刷题
青训营笔记
理tan王子25 天前
伴学笔记 AI刷题 14.数组元素之和最小化 | 豆包MarsCode AI刷题
青训营笔记
理tan王子25 天前
伴学笔记 AI刷题 25.DNA序列编辑距离 | 豆包MarsCode AI刷题
青训营笔记
理tan王子25 天前
伴学笔记 AI刷题 9.超市里的货物架调整 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵1 个月前
分而治之,主题分片Partition | 豆包MarsCode AI刷题
青训营笔记
三六1 个月前
刷题漫漫路(二)| 豆包MarsCode AI刷题
青训营笔记
tabzzz1 个月前
突破Zustand的局限性:与React ContentAPI搭配使用
前端·青训营笔记
Serendipity5651 个月前
Go 语言入门指南——单元测试 | 豆包MarsCode AI刷题;
青训营笔记
wml1 个月前
前端实践-使用React实现简单代办事项列表 | 豆包MarsCode AI刷题
青训营笔记
用户44710308932421 个月前
详解前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记