• 一文详解Gorm


    文章目录

    Gorm入门到精通

    ORM框架介绍和选择

    1.1 ORM介绍和选择

    ORM是“对象-关系-映射”的简称,Go语言中常用的ORM框架有很多,如gorm、facebook-ent、xorm、upper/db、gorose等,这里以gorm为例。

    gorm

    注: 本文内容基于官方文档, 由于迭代原因,建议以最新官方文档为主

    老牌国产Golang orm框架。支持主流关系型数据库。中文文档适合新人入手,国内使用较多。最新版本2.x,比1.x有较大改动

    注意:Gorm最新地址为https://github.com/go-gorm/gorm,之前https://github.com/jinzhu/gorm地址为v1旧版本

    Gorm最新源码地址:https://github.com/go-gorm/gorm

    V1版本地址:https://github.com/jinzhu/gorm

    中文文档地址:https://gorm.io/zh_CN/

    1.2 Gorm介绍

    Gorm 是 Golang 的一个 orm 框架。ORM 是通过实例对象的语法,完成关系型 数据库的操作,是"对象-关系映射"(Object/Relational Mapping) 的缩写。使用 ORM 框架可以让我们更方便的操作数据库。

    Gorm官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server

    作者是中国人,中文文档齐全,对开发者友好,支持主流数据库。

    • 全功能 ORM
    • 关联 (拥有一个,拥有多个,属于,多对多,多态,单表继承)
    • Create,Save,Update,Delete,Find 中钩子方法
    • 支持 Preload、Joins 的预加载
    • 事务,嵌套事务,Save Point,Rollback To to Saved Point
    • Context、预编译模式、DryRun 模式
    • 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
    • SQL 构建器,Upsert,锁,Optimizer/Index/Comment Hint,命名参数,子查询
    • 复合主键,索引,约束
    • 自动迁移
    • 自定义 Logger
    • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
    • 每个特性都经过了测试的重重考验
    • 开发者友好

    二 gorm连接数据库

    go get -u gorm.io/gorm  // gorm
    go get gorm.io/driver/mysql  // 驱动
    
    • 1
    • 2

    2.1 快速链接mysql

    package main
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    )
    
    func main() {
    	dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
        // loc 设置本地系统的时间, 前提 parseTime=True
        // 更多参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 
    	_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    	//db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger:logger.Default.LogMode(logger.Info)})  打印sql日志
    	if err != nil {
    		panic(err) // 如果数据库不存在会报错
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.2 其他配置

    **注意:**想要正确的处理 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{})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.3 加入日志打印sql

    Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误

    Logger 接受的选项不多,您可以在初始化时自定义它,例如:

    newLogger := logger.New(
      log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
      logger.Config{
        SlowThreshold: time.Second,   // 慢 SQL 阈值
        LogLevel:      logger.Silent, // 日志级别  不输出日志
        IgnoreRecordNotFoundError: true,   // 忽略ErrRecordNotFound(记录未找到)错误
        Colorful:      false,         // 禁用彩色打印
      },
    )
    
    // 全局模式
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: newLogger
    })
    
    // 新建会话模式
    tx := db.Session(&Session{Logger: newLogger})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    日志级别

    GORM 定义了这些日志级别:SilentErrorWarnInfo

    db, err := gorm.Open(mysql.Open("test.db"), &gorm.Config{Logger: newLogger})
    
    • 1

    Debug

    Debug 单个操作,将当前操作的 log 级别调整为 logger.Info

    db.Debug().Where("name = ?", "jinzhu").First(&User{})
    
    • 1
    具体代码
    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type  User struct {
    	ID int
    }
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err) // 如果数据库不存在会报错
    	}
        // 数据库迁移
    	db.AutoMigrate(&User{})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    三 automigrate功能

    AutoMigrate自动创建表,可以打开Navicat查看表结构

    AutoMigrate 自动创建表目前而言还有部分bug,没有Django的完善,例如: 无法自动识别修改字段, 无法删除表字段,并且有以下特殊约定

    1. 默认以结构体名字小写 +s 作为表名
    2. 字段名go中是驼峰,mysql中换成下划线
    3. go中字段类型会自动映射成mysql的不同类型,有默认的对应关系

    gorm.Model结构体嵌套

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type Product struct {
    	gorm.Model 
    	Code  string
    	Price uint
    }
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err) // 如果数据库不存在会报错
    	}
    	db.AutoMigrate(&Product{}) // 可以加多个
        
        // 可以通过Set设置附加参数,下面设置表的存储引擎为InnoDB
    	db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    四 快速增删改查

    4.1 快速增删改查

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type Product struct {
    	gorm.Model
    	Code  string
    	Price uint
    }
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err) // 如果数据库不存在会报错
    	}
    	//1 迁移 表,可以写多个
    	//db.AutoMigrate(&Product{})
    
    	//2 插入数据
    	db.Create(&Product{Code: "D42", Price: 100})
    	db.Create(&Product{Code: "D43", Price: 150})
    
    	//3 查询数据
    	var product Product  // 定义空Product结构体对象
    	db.First(&product, 1) // 根据整型主键查找
    	db.First(&product, "code = ?", "F43") // 查找 code 字段值为 D43 的记录
    	//fmt.Println(product)
    
    	//4  更新 - 将 product 的 price 更新为 200
    	db.Model(&product).Update("Price", 200)
    	// Update - 更新多个字段
    	db.Model(&product).Updates(Product{Price: 200, Code: "F43"}) // 仅更新非零值字段
    	db.Model(&product).Updates(map[string]interface{}{"Price": 300, "Code": "F42"})
    
    	//5  Delete - 删除 product
    	db.Delete(&product, 1)  // 软删除
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    4.2 逻辑删除

    只要使用了gorm.Model结构体继承,DeletedAt DeletedAt `gorm:"index"`  字段
    执行删除是其实是update语句,并没有真正的删除
    
    • 1
    • 2

    五 零值问题

    var product Product
    db.First(&product)
    db.Model(&product).Updates(Product{Price: 200, Code: ""}) // 仅更新非零值字段
    // UPDATE `products` SET `updated_at`='2022-09-03 23:48:51.96',`price`=200 WHERE `products`.`deleted_at` IS NULL AND `id` = 2
    
    // 可以看到Code字段不会更新,这是合理的,因为如果零值字段也更新,Product表中好多数据都会被更新为空
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.1 使用sql.NullString更新

    //表模型修改为
    type Product struct {
    	gorm.Model
    	Code  sql.NullString
    	Price uint
    }
    // 修改语句为
    db.Model(&product).Updates(Product{Price: 200, Code: sql.NullString{"",true}}) 	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5.2 使用指针解决

    //表模型修改为
    type Product struct {
    	gorm.Model
    	Code  *string
    	Price uint
    }
    // 修改语句为
    var product Product
    db.First(&product)
    var empty = ""
    db.Model(&product).Updates(Product{Price: 200, Code: &empty}) // 仅更新非零值字段
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    六 表结构定义细节

    6.1 模型定义

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

    例如:

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    6.2 约定

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

    遵循 GORM 已有的约定,可以减少您的配置和代码量。如果约定不符合您的需求,GORM 允许您自定义配置它们

    6.3 gorm.Model

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

    // gorm.Model 的定义
    type Model struct {
      ID        uint           `gorm:"primaryKey"`
      CreatedAt time.Time
      UpdatedAt time.Time
      DeletedAt gorm.DeletedAt `gorm:"index"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    6.4 高级选项

    字段级权限控制

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

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

    type User struct {
      Name string `gorm:"<-:create"` // allow read and create
      Name string `gorm:"<-:update"` // allow read and update
      Name string `gorm:"<-"`        // allow read and write (create and update)
      Name string `gorm:"<-:false"`  // allow read, disable write permission
      Name string `gorm:"->"`        // readonly (disable write permission unless it configured)
      Name string `gorm:"->;<-:create"` // allow read and create
      Name string `gorm:"->:false;<-:create"` // createonly (disabled read from db)
      Name string `gorm:"-"`            // ignore this field when write and read with struct
      Name string `gorm:"-:all"`        // ignore this field when write, read and migrate with struct
      Name string `gorm:"-:migration"`  // ignore this field when migrate with struct
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    创建/更新时间追踪(纳秒、毫秒、秒、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"`      // 使用时间戳秒数填充创建时间
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    嵌入结构体

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

    type User struct {
      gorm.Model
      Name string
    }
    // 等效于
    type User struct {
      ID        uint           `gorm:"primaryKey"`
      CreatedAt time.Time
      UpdatedAt time.Time
      DeletedAt gorm.DeletedAt `gorm:"index"`
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对于正常的结构体字段,你也可以通过标签 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    并且,您可以使用标签 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    字段标签

    声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格

    标签名说明
    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指定列作为unique
    default指定列的默认值
    precision指定列的精度
    scale指定列的比例
    not null指定列不为空
    autoIncrement指定列自增
    autoIncrementIncrement自动递增步长,控制连续列值之间的间隔
    embedded嵌入字段
    embeddedPrefix嵌入嵌入字段的字段列名前缀
    autoCreateTime跟踪当前时间创建时,对于’int’字段,它将跟踪unix秒,使用值’nano/'milli跟踪unix nano/milli秒,如: autoCreateTime:nano
    autoUpdateTime在创建/更新时跟踪当前时间,对于’int’字段,它将跟踪unix秒,使用值’nano/'milli跟踪unix nano/milli秒, 如: autoUpdateTime:milli
    index使用选项创建索引,对多个字段使用相同的名称创建复合索引, 详情参照 Indexes
    uniqueIndex与’index’相同,但创建唯一索引
    check创建检查约束, 如: check:age > 13, 参照 Constraints
    <-设置字段的写入权限, <-:create 仅创建字段, <-:update 仅更新字段, <-:false 没有写权限, <- 创建和更新权限
    ->设置字段读权限, ->:false 没有读权限
    -忽略该字段, - 没有读写权限, -:migration 没有迁移权限, -:all 没有 read/write/migrate 权限
    comment迁移时为字段添加注释

    6.5 案例

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	UserId uint `gorm:"primaryKey"`
    	Name string `gorm:"column:user_name;type:varchar(60)"`
    	Gender uint `gorm:"index"`
    }
    
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err) // 如果数据库不存在会报错
    	}
    	db.AutoMigrate(&User{}) 
      // 如果表之前存在会修改,但是只会修改之前存在的字段的属性,无法识别删除字段
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    七 Gorm增加操作

    我们尽量按照官方文档来看,网上教程都是老版本居多,有时候误差还挺大的
    在这里插入图片描述

    package main
    
    import (
    	"database/sql"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"time"
    )
    
    type User struct {
    	gorm.Model
    	Name         string
    	Age          uint8
    	Birthday     time.Time
    }
    
    func main() {
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    	if err != nil {
    		panic("failed to connect database")
    	}
    	db.AutoMigrate(&User{})
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    7.1 创建记录

    user := User{Name: "lxx", Age: 18, Birthday: time.Now()}
    result := db.Create(&user) // 通过数据的指针来创建
    fmt.Println(user.ID) // 返回插入数据的主键
    fmt.Println(result.Error)        // 返回 error
    fmt.Println(result.RowsAffected)  // 返回插入记录的条数
    
    • 1
    • 2
    • 3
    • 4
    • 5

    7.2 用指定的字段创建记录

    创建记录并更新给出的字段。

    user := User{Name: "json", Age: 19, Birthday: time.Now()}
    db.Select("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("json", 19, "2022-09-04 11:05:21.775")
    
    • 1
    • 2
    • 3

    创建一个记录且一同忽略传递给略去的字段值。

    user := User{Name: "dlrb", Age: 20, Birthday: time.Now()}
    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`updated_at`,`deleted_at`,`birthday`) VALUES ('2022-09-04 18:29:07.531',NULL,'2022-09-04 18:29:07.53')
    
    • 1
    • 2
    • 3

    7.3 批量插入

    要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 GORM 将生成单独一条SQL语句来插入所有数据,并回填主键的值,钩子方法也会被调用。

    t,_:=time.Parse("2006-01-02","1998-07-12")
    var users = []User{{Name: "彭于晏",Birthday: t}, {Name: "llnb",Birthday: t}, {Name: "古天乐",Birthday: t}}
    db.Create(&users)  // create 不仅可以放对象指针,也能放切片的指针。批量创建
    for _, user := range users {
    	fmt.Println(user.ID)
    }
    因为Mysql5.7版本及以上版本的datetime值不能为'0000-00-00 00:00:00',
    //处理方法:
    修改mysql.ini
    在[mysqld]添加一项:
    sql_mode=NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用 CreateInBatches 分批创建时,你可以指定每批的数量,例如:

    	var users [20]User
    	t, _ := time.Parse("2006-01-02", "1999-07-12")
    	for i, _ := range users {
    		users[i].Name = fmt.Sprintf("lxx__%d号", i)
    		users[i].Birthday = t
    	}
    	fmt.Println(users)
    
    	db.CreateInBatches(&users,10)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    7.4 创建钩子

    GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法,请参考 Hooks 中关于生命周期的详细信息

    // main中
    db.Create(&User{
    		Name: "lxx_nb",
    		Birthday: time.Now(),
    })
    // 插入之前的钩子函数
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    	fmt.Println("User被插入了")
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果您想跳过 钩子 方法,您可以使用 SkipHooks 会话模式,例如:

    DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
    
    DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
    
    DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    7.5 根据 Map 创建

    GORM 支持根据 map[string]interface{}[]map[string]interface{}{} 创建记录,例如:

    // 此处可以不传Birthday因为它是null的
    	db.Model(&User{}).Create(map[string]interface{}{
    		"Name": "lxx_001", "Age": 18,
    	})
    	// batch insert from `[]map[string]interface{}{}`
    	db.Model(&User{}).Create([]map[string]interface{}{
    		{"Name": "lxx_002", "Age": 18},
    		{"Name": "lxx_003", "Age": 20},
    	})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意: 根据 map 创建记录时,association(外键查询) 不会被调用,且主键也不会自动填充

    7.6 使用 SQL 表达式、Context Valuer 创建记录(了解)

    GORM 允许使用 SQL 表达式插入数据,有两种方法实现这个目标。根据 map[string]interface{}自定义数据类型 创建,例如:

    // 通过 map 创建记录
    db.Model(User{}).Create(map[string]interface{}{
      "Name": "jinzhu",
      "Location": clause.Expr{SQL: "ST_PointFromText(?)", Vars: []interface{}{"POINT(100 100)"}},
    })
    // INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));
    
    // 通过自定义类型创建记录
    type Location struct {
        X, Y int
    }
    
    // Scan 方法实现了 sql.Scanner 接口
    func (loc *Location) Scan(v interface{}) error {
      // Scan a value into struct from database driver
    }
    
    func (loc Location) GormDataType() string {
      return "geometry"
    }
    
    func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
      return clause.Expr{
        SQL:  "ST_PointFromText(?)",
        Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
      }
    }
    
    type User struct {
      Name     string
      Location Location
    }
    
    db.Create(&User{
      Name:     "jinzhu",
      Location: Location{X: 100, Y: 100},
    })
    // INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    7.7 高级选项

    关联创建(详见后面关联关系)

    创建关联数据时,如果关联值是非零值,这些关联会被 upsert,且它们的 Hook 方法也会被调用

    type CreditCard struct {
      gorm.Model
      Number   string
      UserID   uint
    }
    
    type User struct {
      gorm.Model
      Name       string
      CreditCard CreditCard
    }
    
    db.Create(&User{
      Name: "jinzhu",
      CreditCard: CreditCard{Number: "411111111111"}
    })
    // INSERT INTO `users` ...
    // INSERT INTO `credit_cards` ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    您也可以通过 SelectOmit 跳过关联保存,例如:

    db.Omit("CreditCard").Create(&user)
    
    // 跳过所有关联
    db.Omit(clause.Associations).Create(&user)
    
    • 1
    • 2
    • 3
    • 4
    默认值

    您可以通过标签 default 为字段定义默认值,如:

    type User struct {
      ID   int64
      Name string `gorm:"default:galeone"`
      Age  int64  `gorm:"default:18"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段

    注意 对于声明了默认值的字段,像 0''false 等零值是不会保存到数据库。您需要使用指针类型或 Scanner/Valuer 来避免这个问题,例如:

    type User struct {
      gorm.Model
      Name string
      Age  *int           `gorm:"default:18"`
      Active sql.NullBool `gorm:"default:true"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意 若要数据库有默认、虚拟/生成的值,你必须为字段设置 default 标签。若要在迁移时跳过默认值定义,你可以使用 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:(-);"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用虚拟/生成的值时,你可能需要禁用它的创建、更新权限,查看 字段级权限 获取详情

    Upsert 及冲突(了解)

    GORM 为不同数据库提供了兼容的 Upsert 支持

    有时候插入数据,报主键冲突,有upsert 既可以更新数据,又可以插入数据,来解决这个问题,如果主键存在就更新

    import "gorm.io/gorm/clause"
    
    // 在冲突时,什么都不做
    db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)
    
    // 在`id`冲突时,将列更新为默认值
    db.Clauses(clause.OnConflict{
      Columns:   []clause.Column{{Name: "id"}},
      DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}),
    }).Create(&users)
    // MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
    // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL
    
    // 使用SQL语句
    db.Clauses(clause.OnConflict{
      Columns:   []clause.Column{{Name: "id"}},
      DoUpdates: clause.Assignments(map[string]interface{}{"count": gorm.Expr("GREATEST(count, VALUES(count))")}),
    }).Create(&users)
    // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));
    
    // 在`id`冲突时,将列更新为新值
    db.Clauses(clause.OnConflict{
      Columns:   []clause.Column{{Name: "id"}},
      DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
    }).Create(&users)
    // MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
    // INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
    // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age=VALUES(age); MySQL
    
    // 在冲突时,更新除主键以外的所有列到新值----》这个用的多
    db.Clauses(clause.OnConflict{
      UpdateAll: true,
    }).Create(&users)
    // INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    您还可以查看 高级查询 中的 FirstOrInitFirstOrCreate

    查看 原生 SQL 及构造器 获取更多细节

    八 Gorm查询

    8.1 检索单个对象

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

    	var user User
    	// 获取第一条记录(主键升序)
    	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;
    	fmt.Println(user)
    
    	result := db.First(&user)
    	fmt.Println(result.RowsAffected) // 返回找到的记录数
    	fmt.Println(result.Error)       // returns error or nil
    
    	// 检查 返回的错误是否是没找到记录的错误  ErrRecordNotFound 错误
    	fmt.Println(errors.Is(result.Error, gorm.ErrRecordNotFound))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user)Find方法可以接受struct和slice的数据。

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

    var user User
    var users []User
    
    // works because destination struct is passed in
    // 因为user自动和User结构体关联了
    db.First(&user)
    // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
    
    // works because model is specified using `db.Model()`
    // 使用map,需要指定跟那个表关联
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result)
    // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
    
    // doesn't work-->没指定Model,无效
    result := map[string]interface{}{}
    db.Table("users").First(&result)
    
    // works with Take,使用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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    用主键检索

    如果主键是数字类型,您可以使用 内联条件 来检索对象。 传入字符串参数时,需要特别注意 SQL 注入问题,查看 安全 获取详情.

    	var user User
    	var users []User
    	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);
    	fmt.Println(user)
    	fmt.Println(users)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

    当目标对象有主值时,主键将用于构建条件,例如:

    //当目标对象具有主键值时,主键将用于构建条件
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    8.2 检索全部对象

    	var users []User
    	// Get all records
    	result := db.Find(&users)
    	// SELECT * FROM users;
    
    	fmt.Println(result.RowsAffected) // returns found records count, equals `len(users)`
    	fmt.Println(result.Error)        // returns error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    8.3 条件

    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';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意 使用 struct 查询时,GORM 只会查询非零字段,这意味着如果您的字段的值为 0''false 或其他 [零值](https:// tour.golang.org/basics/12),它不会用于构建查询条件,例如:

    // 当使用结构体作为查询条件,gorm只会查询非0字段,如果字段是`0`, `''`, `false` or other zero values,该字段不会被用于构建查询条件
    db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu";
    
    • 1
    • 2
    • 3

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

    // 如果想要在查询中包含0的字段,可以使用map来做
    db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
    
    • 1
    • 2
    • 3

    更多详情请参见指定结构搜索字段

    指定结构体查询字段

    使用 struct 进行搜索时,您可以通过将相关字段名称或 dbname 传递给 Where() 来指定要在查询条件中使用的 struct 中的哪些特定值,例如:

    //在使用struct进行搜索时,可以通过将相关字段名或数据库名传递给`Where(),来指定在查询条件中使用struct中的哪些特定值`
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    内联条件

    查询条件可以以类似于Where的方式内联到’First’和’Find’等方法中

    // 如果是非整形主键,根据主键获取记录
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    Not 条件

    构建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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    对于更复杂的 SQL 查询。另请参考高级查询中的分组条件

    8.4 选择特定字段

    `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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    另请查看 Smart Select Fields

    8.5 Order

    指定从数据库检索记录时的顺序

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    8.6 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    分页器的制作方法参考分页器

    8.7 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    8.8 Distinct

    从模型中选择不同的值, 去重

    db.Distinct("name", "age").Order("name, age desc").Find(&results)
    
    • 1

    Distinct works with Pluck and Count too

    8.9 Joins

    Specify Joins conditions-指定连接条件

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    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`;
    
    • 1
    • 2

    加入条件

    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;
    
    • 1
    • 2

    更多详情请参考预加载(Eager Loading)

    Joins a Derived Table

    还可以使用 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    8.10 Scan

    将结果扫描到结构体中的工作方式,与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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    九 高级查询(了解)

    9.1 智能选择字段

    GORM 允许通过 Select 方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段,例如:

    type User struct {
      ID     uint
      Name   string
      Age    int
      Gender string
      // 假设后面还有几百个字段...
    }
    
    type APIUser struct {
      ID   uint
      Name string
    }
    
    // 查询时会自动选择 `id`, `name` 字段
    db.Model(&User{}).Limit(10).Find(&APIUser{})
    // SELECT `id`, `name` FROM `users` LIMIT 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意 QueryFields 模式会根据当前 model 的所有字段名称进行 select。

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
      QueryFields: true,
    })
    
    db.Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users` // 带上这个选项
    
    // Session Mode
    db.Session(&gorm.Session{QueryFields: true}).Find(&user)
    // SELECT `users`.`name`, `users`.`age`, ... FROM `users`
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    9.2 Locking (FOR UPDATE)

    GORM 支持多种类型的锁,例如:

    db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
    // SELECT * FROM `users` FOR UPDATE
    
    db.Clauses(clause.Locking{
      Strength: "SHARE",
      Table: clause.Table{Name: clause.CurrentTable},
    }).Find(&users)
    // SELECT * FROM `users` FOR SHARE OF `users`
    
    db.Clauses(clause.Locking{
      Strength: "UPDATE",
      Options: "NOWAIT",
    }).Find(&users)
    // SELECT * FROM `users` FOR UPDATE NOWAIT
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    查看 原生 SQL 及构造器 获取详情

    9.3 子查询

    子查询可以嵌套在查询中,GORM 允许在使用 *gorm.DB 对象作为参数时生成子查询

    db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
    // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
    
    subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
    db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
    // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    From 子查询

    GORM 允许您在 Table 方法中通过 FROM 子句使用子查询,例如:

    db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18}).Find(&User{})
    // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18
    
    subQuery1 := db.Model(&User{}).Select("name")
    subQuery2 := db.Model(&Pet{}).Select("name")
    db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
    // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    9.4 Group 条件

    使用 Group 条件可以更轻松的编写复杂 SQL

    db.Where(
        db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
    ).Or(
        db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
    ).Find(&Pizza{}).Statement
    
    // SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    9.5 带多个列的 In

    带多个列的 In 查询

    db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
    // SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
    
    • 1
    • 2

    9.6 命名参数

    GORM 支持 sql.NamedArgmap[string]interface{}{} 形式的命名参数,例如:

    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"
    
    db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查看 原生 SQL 及构造器 获取详情

    9.7 Find 至 map

    GORM 允许扫描结果至 map[string]interface{}[]map[string]interface{},此时别忘了指定 ModelTable,例如:

    result := map[string]interface{}{}
    db.Model(&User{}).First(&result, "id = ?", 1)
    
    var results []map[string]interface{}
    db.Table("users").Find(&results)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    9.8 FirstOrInit

    获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持 sturct 和 map 条件)

    // 未找到 user,则根据给定的条件初始化一条记录
    db.FirstOrInit(&user, User{Name: "non_existing"})
    // user -> User{Name: "non_existing"}
    
    // 找到了 `name` = `jinzhu` 的 user
    db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    
    // 找到了 `name` = `jinzhu` 的 user
    db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果没有找到记录,可以使用包含更多的属性的结构体初始化 user,Attrs 不会被用于生成查询 SQL

    // 未找到 user,则根据给定的条件以及 Attrs 初始化 user
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // user -> User{Name: "non_existing", Age: 20}
    
    // 未找到 user,则根据给定的条件以及 Attrs 初始化 user
    db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user)
    // SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // user -> User{Name: "non_existing", Age: 20}
    
    // 找到了 `name` = `jinzhu` 的 user,则忽略 Attrs
    db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    // SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    不管是否找到记录,Assign 都会将属性赋值给 struct,但这些属性不会被用于生成查询 SQL,也不会被保存到数据库

    // 未找到 user,根据条件和 Assign 属性初始化 struct
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // user -> User{Name: "non_existing", Age: 20}
    
    // 找到 `name` = `jinzhu` 的记录,依然会更新 Assign 相关的属性
    db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
    // SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "Jinzhu", Age: 20}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    9.9 FirstOrCreate

    获取第一个匹配记录或在给定条件下创建一个新记录(仅适用于结构、映射条件),RowsAffected 返回创建/更新记录的计数

    // User not found, create a new record with give conditions
    result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 0
    
    // Found user with `name` = `jinzhu`
    result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
    // user -> User{ID: 111, Name: "jinzhu", "Age": 18}
    // result.RowsAffected // => 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果没有找到记录,可以使用包含更多的属性的结构体创建记录,Attrs 不会被用于生成查询 SQL 。

    // 未找到 user,根据条件和 Assign 属性创建记录
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}
    
    // 找到了 `name` = `jinzhu` 的 user,则忽略 Attrs
    db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    // user -> User{ID: 111, Name: "jinzhu", Age: 18}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    不管是否找到记录,Assign 都会将属性赋值给 struct,并将结果写回数据库

    // 未找到 user,根据条件和 Assign 属性创建记录
    db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
    // INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
    // user -> User{ID: 112, Name: "non_existing", Age: 20}
    
    // 找到了 `name` = `jinzhu` 的 user,依然会根据 Assign 更新记录
    db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    // UPDATE users SET age=20 WHERE id = 111;
    // user -> User{ID: 111, Name: "jinzhu", Age: 20}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    9.10 优化器、索引提示

    优化器提示用于控制查询优化器选择某个查询执行计划,GORM 通过 gorm.io/hints 提供支持,例如:

    import "gorm.io/hints"
    
    db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
    // SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
    
    • 1
    • 2
    • 3
    • 4

    索引提示允许传递索引提示到数据库,以防查询计划器出现混乱。

    import "gorm.io/hints"
    
    db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
    // SELECT * FROM `users` USE INDEX (`idx_user_name`)
    
    db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
    // SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    参考 优化器提示、索引、备注 获取详情

    9.11 迭代

    GORM 支持通过行进行迭代

    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
    defer rows.Close()
    
    for rows.Next() {
      var user User
      // ScanRows 方法用于将一行记录扫描至结构体
      db.ScanRows(rows, &user)
    
      // 业务逻辑...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    9.12 FindInBatches

    用于批量查询并处理记录

    // 每次批量处理 100 条
    result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
      for _, result := range results {
        // 批量处理找到的记录
      }
    
      tx.Save(&results)
    
      tx.RowsAffected // 本次批量操作影响的记录数
    
      batch // Batch 1, 2, 3
    
      // 如果返回错误会终止后续批量操作
      return nil
    })
    
    result.Error // returned error
    result.RowsAffected // 整个批量操作影响的记录数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    9.13 查询钩子

    对于查询操作,GORM 支持 AfterFind 钩子,查询记录后会调用它,详情请参考 钩子

    func (u *User) AfterFind(tx *gorm.DB) (err error) {
      if u.Role == "" {
        u.Role = "user"
      }
      return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    9.14 Pluck

    Pluck 用于从数据库查询单个列,并将结果扫描到切片。如果您想要查询多列,您应该使用 SelectScan

    var ages []int64
    db.Model(&users).Pluck("age", &ages)
    
    var names []string
    db.Model(&User{}).Pluck("name", &names)
    
    db.Table("deleted_users").Pluck("name", &names)
    
    // Distinct Pluck
    db.Model(&User{}).Distinct().Pluck("Name", &names)
    // SELECT DISTINCT `name` FROM `users`
    
    // 超过一列的查询,应该使用 `Scan` 或者 `Find`,例如:
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    9.15 Scope

    Scopes 允许你指定常用的查询,可以在调用方法时引用这些查询

    func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
      return db.Where("amount > ?", 1000)
    }
    
    func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
      return db.Where("pay_mode_sign = ?", "C")
    }
    
    func PaidWithCod(db *gorm.DB) *gorm.DB {
      return db.Where("pay_mode_sign = ?", "C")
    }
    
    func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
      return func (db *gorm.DB) *gorm.DB {
        return db.Where("status IN (?)", status)
      }
    }
    
    db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
    // 查找所有金额大于 1000 的信用卡订单
    
    db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
    // 查找所有金额大于 1000 的货到付款订单
    
    db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
    // 查找所有金额大于 1000 且已付款或已发货的订单
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    查看 Scopes 获取详情

    9.16 Count

    Count 用于获取匹配的记录数

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    十 更新

    // 表模型变成
    type User struct {
    	gorm.Model
    	Name         string
    	Age          uint8
    	Birthday     time.Time
    	Active       bool
    }
    // 删除表,重新迁移
    db.AutoMigrate(&User{})
    // 插入记录
    	var user =User{Name: "lxx",Age: 18,Birthday: time.Now(),Active: true}
    	db.Create(&user)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    10.1 保存所有字段

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

    var user User
    db.First(&user)
    
    user.Name = "lxx"
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    10.2 更新单个列

    当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 错误,查看 Block Global Updates 获取详情。当使用了 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 是 `1`
    var user User
    db.First(&user)
    db.Model(&user).Update("name", "lxx_nb")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    // 根据条件和 model 的值进行更新
    var user User
    db.First(&user)
    db.Model(&user).Where("active = ?", true).Update("name", "lxx_hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    10.3 更新多列

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

    // 根据 `struct` 更新属性,只会更新非零值的字段
    	var user User
    	db.First(&user)
    	db.Model(&user).Updates(User{Name: "hello", Age: 99, Active: false})
    // UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
    
    // 根据 `map` 更新属性
    var user User
    db.First(&user)
    db.Model(&user).Updates(map[string]interface{}{"name": "lxx_01", "age": 88, "active": false})
    // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意 当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作

    10.4 更新选定字段

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

    // 使用 Map 进行 Select
    // User's ID is `111`:
    var user User
    db.First(&user)
    // 只更新name
    db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "lxx_002", "age": 66, "active": false})
    // UPDATE users SET name='hello' WHERE id=111;
    
    var user User
    db.First(&user)
    // 除了name都更新
    db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": true})
    }
    // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    // 使用 Struct 进行 Select(会 select 零值的字段),零值字段也会更新
    // 注意 user的id不能为0
    var user User
    db.First(&user)
    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 所有字段(查询包括零值字段的所有字段)
    var user User
    db.First(&user)
    fmt.Println(user)
    db.Model(&user).Select("*").Updates(User{Name: "lxx_004", Birthday:time.Now(), Age: 33,Active: false, Model:gorm.Model{ID:3,CreatedAt: time.Now(),UpdatedAt: time.Now()}})
    
    
    
    // Select 除 "CreatedAt","birthday"外的所有字段(包括零值字段的所有字段),注意id会变成0
    db.Model(&user).Select("*").Omit("CreatedAt","birthday").Updates(User{Name: "jinzhu", Age: 0})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    10.5 更新 Hook

    对于更新操作,GORM 支持 BeforeSaveBeforeUpdateAfterSaveAfterUpdate 钩子,这些方法将在更新记录时被调用,详情请参阅 钩子

    func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
        if u.Role == "admin" {
            return errors.New("admin user not allowed to update")
        }
        return
    }
    // 测试
    func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
    	fmt.Println("修改了")
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    10.6 批量更新

    如果您尚未通过 Model 指定记录的主键,则 GORM 会执行批量更新

    // 根据 struct 更新--->批量更新不能用Hook
    db.Model(User{}).Where("active = ?", true).Updates(User{Name: "hello", Age: 18})
    //  UPDATE `users` SET `updated_at`='2022-05-05 01:01:53.001',`name`='hello',`age`=18 WHERE active = true AND `users`.`deleted_at` IS NULL
    
    
    // 根据 map 更新
    db.Table("users").Where("id IN ?", []int{1, 2}).Updates(map[string]interface{}{"name": "lxx_009", "age": 99})
    
    // UPDATE `users` SET `age`=99,`name`='lxx_009' WHERE id IN (1,2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    阻止全局更新

    如果在没有任何条件的情况下执行批量更新,默认情况下,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
    
    
    // 方式一:直接执行原生sql可以全局更新
    db.Exec("UPDATE users SET name = ?", "jinzhu")
    // UPDATE users SET name = "jinzhu"
    
    // 方式二:使用session
    db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "lxx_010")
    // UPDATE users SET `name` = "lxx_010"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    更新的记录数

    获取受更新影响的行数

    // 通过 `RowsAffected` 得到更新的记录数
    result := db.Model(User{}).Where("active = ?", true).Updates(User{Name: "hello", Age: 18})
    fmt.Println(result.RowsAffected) // 更新的记录数
    fmt.Println(result.Error) // 错误
    
    • 1
    • 2
    • 3
    • 4

    10.7 高级选项(了解)

    使用 SQL 表达式更新

    GORM 允许使用 SQL 表达式更新列,例如:

    // user 的 ID 是 `1`
    var user User
    db.First(&user)
    db.Model(&user).Update("age", gorm.Expr("age * ? + ?", 2, 100))
    //  UPDATE `users` SET `age`=age * 2 + 100,`updated_at`='2022-05-05 01:11:16.242' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
    
    
    
    var user User
    db.First(&user)
    db.Model(&user).Updates(map[string]interface{}{"age": gorm.Expr("age - ? + ?", 2, 100)})
    //  UPDATE `users` SET `age`=age - 2 + 100,`updated_at`='2022-05-05 01:13:13.302' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
    
    db.First(&user)
    db.Model(&user).UpdateColumn("age", gorm.Expr("age - ?", 10))
    // UPDATE `users` SET `age`=age - 10 WHERE `users`.`deleted_at` IS NULL AND `id` = 1
    
    var user User
    db.First(&user)
    db.Model(&user).Where("age > 100").UpdateColumn("age", gorm.Expr("age - ?", 100))
    // UPDATE `users` SET `age`=age - 100 WHERE age > 100 AND `users`.`deleted_at` IS NULL AND `id` = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    并且 GORM 也允许使用 SQL 表达式、自定义数据类型的 Context Valuer 来更新,例如:

    // 根据自定义数据类型创建
    type Location struct {
        X, Y int
    }
    
    func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
      return clause.Expr{
        SQL:  "ST_PointFromText(?)",
        Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
      }
    }
    
    db.Model(&User{ID: 1}).Updates(User{
      Name:  "jinzhu",
      Location: Location{X: 100, Y: 100},
    })
    // UPDATE `user_with_points` SET `name`="jinzhu",`location`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    根据子查询进行更新

    使用子查询更新表

    db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
    // UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
    
    db.Table("users as u").Where("name = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
    //UPDATE users as u SET `company_name`=(SELECT name FROM companies as c WHERE c.id = u.company_id) WHERE name = 'jinzhu'
    
    
    
    db.Table("users as u").Where("name = ?", "jinzhu").Updates(map[string]interface{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})
    //UPDATE users as u SET `company_name`=(SELECT name FROM companies as c WHERE c.id = u.company_id) WHERE name = 'jinzhu'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    不使用 Hook 和时间追踪

    如果您想在更新时跳过 Hook 方法且不追踪更新时间,可以使用 UpdateColumnUpdateColumns,其用法类似于 UpdateUpdates

    // 更新单个列
    db.Model(&user).UpdateColumn("name", "hello")
    // UPDATE users SET name='hello' WHERE id = 111;
    
    // 更新多个列
    db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
    // UPDATE users SET name='hello', age=18 WHERE id = 111;
    
    // 更新选中的列
    db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
    // UPDATE users SET name='hello', age=0 WHERE id = 111;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    检查字段是否有变更?

    GORM 提供了 Changed 方法,它可以被用在 Before Update Hook 里,它会返回字段是否有变更的布尔值

    Changed 方法只能与 UpdateUpdates 方法一起使用,并且它只是检查 Model 对象字段的值与 UpdateUpdates 的值是否相等,如果值有变更,且字段没有被忽略,则返回 true

    func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
    	// 如果 age 字段有变更,不允许,直接报错
    	if tx.Statement.Changed("age") {
    		fmt.Println("xx")
    		return errors.New("age not allowed to change")
    	}
    	// 如果 Name 字段有变更,把年龄改为18
    	if tx.Statement.Changed("Name") {
    		tx.Statement.SetColumn("age", 18)
    	}
    
    	// 如果任意字段有变更,修改CreatedAt时间
    	if tx.Statement.Changed() {
    		tx.Statement.SetColumn("CreatedAt", time.Now())
    
    	}
    	return nil
    }
    
    
    
    //修改名字,把age 设为18
    db.Model(&User{Model:gorm.Model{ID: 1} , Name: "lxx"}).Updates(map[string]interface{}{"name": "lxx_nb"})
    
    // 名字修改,但是名字其实并没有变,所以年龄不会被修改
    db.Model(&User{Model:gorm.Model{ID: 1}, Name: "lxx_nb"}).Updates(map[string]interface{}{"name": "lxx_nb"})
    
    //修改名字,把age 设为18
    db.Model(&User{Model:gorm.Model{ID: 1}, Name: "lxx_nb"}).Updates(User{Name: "jinzhu2"})
    
    //名字修改,但是名字其实并没有变,所以年龄不会被修改
    db.Model(&User{Model:gorm.Model{ID: 1}, Name: "jinzhu2"}).Updates(User{Name: "jinzhu2"})
    
    // 不允许修改age
    db.Model(&User{}).Where("id=?",1).Updates(map[string]interface{}{"age": 100})
    
    //任意字段有变更,更新CreatedAt时间
    db.Model(&User{}).Where("id=?",1).Updates(map[string]interface{}{"active": false})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    在 Update 时修改值

    若要在 Before 钩子中改变要更新的值,如果它是一个完整的更新,可以使用 Save;否则,应该使用 SetColumn ,例如:

    func (user *User) BeforeSave(tx *gorm.DB) (err error) {
      if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
        tx.Statement.SetColumn("EncryptedPassword", pw)
      }
    
      if tx.Statement.Changed("Code") {
        user.Age += 20
        tx.Statement.SetColumn("Age", user.Age)
      }
    }
    
    db.Model(&user).Update("Name", "jinzhu")
    var user User
    db.First(&user)
    db.Model(&user).Update("Name", "lxx008")
    
    // 只要更新名字,就把名字加密
    func (user *User) BeforeSave(tx *gorm.DB) (err error) {
    	if genName, err := bcrypt.GenerateFromPassword([]byte(user.Name), 0); err == nil {
    		tx.Statement.SetColumn("name", genName)
    	}
    	return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    十一 删除

    11.1 删除一条记录

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

    // user 的 ID 是 `1`--->软删除
    var user User
    db.First(&user)
    db.Delete(&user)
    
    // UPDATE `users` SET `deleted_at`='2022-05-05 02:05:16.7' WHERE `users`.`id` = 1 AND `users`.`deleted_at` IS NULL
    
    
    // 带额外条件的删除
    var user User
    db.First(&user)
    db.Where("name = ?", "hello").Delete(&user)
    
    //db.Where("name = ?", "hello").Delete(&User{})
    // UPDATE `users` SET `deleted_at`='2022-05-05 02:08:30.857' WHERE name = 'hello' AND `users`.`deleted_at` IS NULL
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    11.2 根据主键删除

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

    // 先插入
    var user =User{Name: "lxx001",Age: 99,Birthday: time.Now()}
    db.Create(&user)
    
    db.Delete(&User{}, 3)
    
    
    db.Delete(&User{}, "10")
    
    b.Delete(&User{}, []int{1,2,3})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    11.3 Delete Hook

    对于删除操作,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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    11.4 批量删除

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

    db.Where("name LIKE ?", "%lxx%").Delete(&User{})
    
    db.Delete(&User{}, "name LIKE ?", "%lxx%")
    
    • 1
    • 2
    • 3
    阻止全局删除

    如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回 ErrMissingWhereClause错误

    对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:

    db.Delete(&User{}).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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    返回删除行的数据(了解)

    返回被删除的数据,仅适用于支持 Returning 的数据库,例如:

    // 返回所有列
    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}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    11.5 软删除

    如果您的模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!

    拥有软删除能力的模型调用 Delete 时,记录不会被数据库。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。

    // 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果您不想引入 gorm.Model,您也可以这样启用软删除特性:

    type User struct {
      ID      int
      Deleted gorm.DeletedAt
      Name    string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    查找被软删除的记录

    您可以使用 Unscoped 找到被软删除的记录

    var users []User
    db.Unscoped().Where("age = 99").Find(&users)
    fmt.Println(users)
    
    • 1
    • 2
    • 3
    永久删除

    您也可以使用 Unscoped 永久删除匹配的记录

    var users []User
    db.Unscoped().Where("age = 99").Find(&users)
    db.Unscoped().Delete(&users)
    
    • 1
    • 2
    • 3
    Delete Flag

    将 unix 时间戳作为 delete flag

    // go get -u gorm.io/plugin/soft_delete
    //db.AutoMigrate(&User2{})
    import "gorm.io/plugin/soft_delete"
    
    type User2 struct {
      ID        uint
      Name      string
      DeletedAt soft_delete.DeletedAt
    }
    
    // 创建
    var user2 User2=User2{Name: "lxx"}
    db.Create(&user2)
    // 删除
    db.Delete(&user2)
    
    // 查询
    SELECT * FROM users WHERE deleted_at = 0;
    
    // 删除
    UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    INFO 在配合 unique 字段使用软删除时,您需要使用这个基于 unix 时间戳的 DeletedAt 字段创建一个复合索引,例如:

    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"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用 1 / 0 作为 delete flag

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    十二 SQL 构建器

    12.1 原生 SQL

    原生查询 SQL 和 Scan

    type User3 struct {
    	ID   int
    	Name string
    	Age  int
    }
    
    // 创建表
    db.AutoMigrate(&User3{})
    // 插入数据
    
    var user User3
    db.Raw("SELECT id, name, age FROM user3 WHERE name = ?", "lxx").Scan(&user)
    fmt.Println(user)
    
    var age int
    db.Raw("SELECT SUM(age) FROM user3 WHERE name like ?", "%lxx%").Scan(&age)
    fmt.Println(age)
    
    var users []User3
    db.Raw("SELECT * FROM user3 WHERE id > ?", 0).Scan(&users)
    fmt.Println(users)
    
    
    // mysql 不支持
    var users []User3
    db.Raw("UPDATE user3 SET name = ? WHERE age = ? RETURNING id, name", "lxx", 12).Scan(&users)
    fmt.Println(users)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    Exec 原生 SQL

    db.Exec("DROP TABLE users")
    db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
    
    // Exec with SQL Expression
    db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意 GORM 允许缓存预编译 SQL 语句来提高性能,查看 性能 获取详情

    12.2 命名参数

    GORM 支持 sql.NamedArgmap[string]interface{}{} 或 struct 形式的命名参数,例如:

    // 测试
    var user []User3
    db.Where("name = @name OR age = @age", sql.Named("name", "lxx"),sql.Named("age", "12")).Find(&user)
    fmt.Println(user)
    	
    var users []User3
    db.Where("name = @name OR age = @age", sql.Named("name", "lxx"),sql.Named("age", "12")).Find(&users)
    fmt.Println(users)
    
    // 其他一样
    db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)
    // SELECT * FROM `users` WHERE name1 = "jinzhu2" OR name2 = "jinzhu2" ORDER BY `users`.`id` LIMIT 1
    
    // 原生 SQL 及命名参数
    db.Raw("SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
       sql.Named("name", "jinzhu1"), sql.Named("name2", "jinzhu2")).Find(&user)
    // SELECT * FROM users WHERE name1 = "jinzhu1" OR name2 = "jinzhu2" OR name3 = "jinzhu1"
    
    db.Exec("UPDATE users SET name1 = @name, name2 = @name2, name3 = @name",
       sql.Named("name", "jinzhunew"), sql.Named("name2", "jinzhunew2"))
    // UPDATE users SET name1 = "jinzhunew", name2 = "jinzhunew2", name3 = "jinzhunew"
    
    db.Raw("SELECT * FROM users WHERE (name1 = @name AND name3 = @name) AND name2 = @name2",
       map[string]interface{}{"name": "jinzhu", "name2": "jinzhu2"}).Find(&user)
    // SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"
    
    type NamedArgument struct {
        Name string
        Name2 string
    }
    
    db.Raw("SELECT * FROM users WHERE (name1 = @Name AND name3 = @Name) AND name2 = @Name2",
         NamedArgument{Name: "jinzhu", Name2: "jinzhu2"}).Find(&user)
    // SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    12.3 DryRun 模式

    在不执行的情况下生成 SQL 及其参数,可以用于准备或测试生成的 SQL,详情请参考 Session

    var user User3
    //session := db.Session(&gorm.Session{DryRun: true})
    //session.First(&user, 1)
    //fmt.Println(user)
    
    stmt := db.Session(&gorm.Session{DryRun: true}).First(&user, 1).Statement
    fmt.Println(stmt.SQL.String()) //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
    fmt.Println(stmt.Vars)         //=> []interface{}{1}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    12.4 ToSQL

    返回生成的 SQL 但不执行。

    GORM使用 database/sql 的参数占位符来构建 SQL 语句,它会自动转义参数以避免 SQL 注入,但我们不保证生成 SQL 的安全,请只用于调试。

    var users []User3
    sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
    		return tx.Model(&User3{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&users)
    	})
    fmt.Println(sql)
    //SELECT * FROM `user3` WHERE id = 100 ORDER BY age desc LIMIT 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    12.5 Row & Rows

    获取 *sql.Row 结果

    // 使用 GORM API 构建 SQL
    var name string
    var age  int
    row := db.Table("user3").Where("name = ?", "lxx").Select("name", "age").Row()
    row.Scan(&name, &age)
    fmt.Println(name)
    fmt.Println(age)
    
    // 使用原生 SQL
    var name string
    var age  int
    row := db.Raw("select name, age from user3 where name = ?", "lxx").Row()
    row.Scan(&name, &age)
    fmt.Println(name)
    fmt.Println(age)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    获取 *sql.Rows 结果

    // 使用 GORM API 构建 SQL
    var name string
    var age int
    rows, _ := db.Model(&User3{}).Where("name like ?", "%lxx%").Select("name, age").Rows()
    defer rows.Close()
    for rows.Next() {
    		rows.Scan(&name, &age)
    		fmt.Printf("name是:%s,age是:%d\n",name,age)
    }
    
    // 原生 SQL
    var name string
    var age int
    rows, err := db.Raw("select name, age from user3 where name like ?", "%lxx%").Rows()
    defer rows.Close()
    for rows.Next() {
    		rows.Scan(&name, &age)
    		fmt.Printf("name是:%s,age是:%d\n",name,age)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    转到 FindInBatches 获取如何在批量中查询和处理记录的信息, 转到 Group 条件 获取如何构建复杂 SQL 查询的信息

    12.6 将 sql.Rows 扫描至 model

    使用 ScanRows 将一行记录扫描至 struct,例如:

    rows, err := db.Model(&User3{}).Where("name like ?", "%lxx%").Select("name, age").Rows()
    defer rows.Close()
    var user User3
    for rows.Next() {
    	// ScanRows 将一行扫描至 user
    	db.ScanRows(rows, &user)
    	fmt.Println(user)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    12.7 Connection

    在同一个 db tcp 连接中运行多个 SQL(不在事务中)

    db.Connection(func(tx *gorm.DB) error {
      tx.Exec("SET my.role = ?", "admin")
    
      tx.First(&User{})
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    12.8 Advanced(了解)

    子句(Clause)

    GORM 使用 SQL builder 在内部生成 SQL,对于每个操作,GORM 都会创建一个 *gorm.Statement 对象,所有 GORM API 都会为 Statement 添加/更改 Clause,最后,GORM 基于这些子句生成 SQL

    例如,当使用 First 查询时,它会将以下子句添加到 Statement

    clause.Select{Columns: "*"}
    clause.From{Tables: clause.CurrentTable}
    clause.Limit{Limit: 1}
    clause.OrderByColumn{
      Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后 GORM 构建最终在 Query 回调中查询 SQL,例如:

    Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")
    
    • 1

    生成SQL:

    SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
    
    • 1

    您可以定义自己的Clause并与GORM一起使用,它需要实现Interface

    查看 示例 以供参考

    子句构造器

    对于不同的数据库,子句可能会产生不同的 SQL,例如:

    db.Offset(10).Limit(5).Find(&users)
    // Generated for SQL Server
    // SELECT * FROM "users" OFFSET 10 ROW FETCH NEXT 5 ROWS ONLY
    // Generated for MySQL
    // SELECT * FROM `users` LIMIT 5 OFFSET 10
    
    • 1
    • 2
    • 3
    • 4
    • 5

    支持是因为 GORM 允许数据库驱动注册 Clause Builder 替换默认的,以 Limit 为例

    子句选项

    GORM 定义了 Many Clauses,并且一些子句提供了高级选项,可用于您的应用程序

    尽管它们中的大多数很少使用,但如果您发现 GORM 公共 API 无法满足您的要求,不妨检查一下,例如:

    db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)
    // INSERT IGNORE INTO users (name,age...) VALUES ("jinzhu",18...);
    
    • 1
    • 2
    StatementModifier

    GORM 提供接口 StatementModifier 允许您修改语句以符合您的要求,采取 [Hints](https://gorm.io /zh_CN/docs/hints.html) 为例

    import "gorm.io/hints"
    
    db.Clauses(hints.New("hint")).Find(&User{})
    // SELECT * /*+ hint */ FROM `users`
    
    • 1
    • 2
    • 3
    • 4

    十三 关联之Belongs To

    13.1 Belongs To

    belongs to 会与另一个模型建立了一对一的连接。 这种模型的每一个实例都“属于”另一个模型的一个实例。

    例如,您的应用包含 user 和 company,并且每个 user 能且只能被分配给一个 company。下面的类型就表示这种关系。 注意,在 User 对象中,有一个和 Company 一样的 CompanyID。 默认情况下, CompanyID 被隐含地用来在 UserCompany 之间创建一个外键关系, 因此必须包含在 User 结构体中才能填充 Company 内部结构体。

    // `User` 属于 `Company`,`CompanyID` 是外键
    type User struct {
      gorm.Model
      Name      string
      CompanyID int  // 必须以 结构体名 + ID 的方式
      Company   Company
    }
    
    type Company struct {
      ID   int
      Name string
    }
    
    
    db.AutoMigrate(&User{},&Company{})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    请参阅 预加载 以了解内部结构的详细信息。

    13.2 重写外键

    要定义一个 belongs to 关系,数据库的表中必须存在外键。默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字

    例如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID。

    GORM同时提供自定义外键名字的方式,如下例所示。

    type User struct {
      gorm.Model
      Name         string
      CompanyRefer int // 外键改名字为CompanyRefer
      Company      Company `gorm:"foreignKey:CompanyRefer"`
      // 使用 CompanyRefer 作为外键
    }
    
    type Company struct {
      ID   int
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    13.3 重写引用(一般不用)

    对于 belongs to 关系,GORM 通常使用数据库表,主表(拥有者)的主键值作为外键参考。 正如上面的例子,我们使用主表Company中的主键字段ID作为外键的参考值。

    如果在Company实体中设置了User实体,那么GORM会自动把Company中的ID属性保存到User的CompanyID属性中。

    同样的,您也可以使用标签 references 来更改它,例如:

    type User struct {
      gorm.Model
      Name      string
      CompanyCode int
      Company   Company `gorm:"foreignKey:CompanyCode;references:Code"` // 指定外键字段为CompanyCode,指定与公司表中Code字段关联
    }
    
    type Company struct {
      ID   int
      Code int `gorm:"primarykey"`
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    13.4 Belongs to 的 CRUD

    点击 关联模式 链接获取 belongs to 相关的用法

    13.5 预加载

    GORM允许通过使用Preload或者Joins来主动加载实体的关联关系,具体内容请参考,预加载(主动加载)

    13.6 外键约束

    你可以通过OnUpdate, OnDelete配置标签来增加关联关系的级联操作,如下面的例子,通过GORM可以完成用户和公司的级联更新和级联删除操作:

    type User struct {
    	gorm.Model
    	Name      string
    	Age int
    	UserDetailID int
    	UserDetail   UserDetail `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` // 级联更新,删除时置空
    }
    
    type UserDetail struct {
    	ID   int
    	Addr string
    }
    
    // Navicat的设计表中可以查看
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    13.7 案例

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	gorm.Model
    	Name      string
    	CompanyID int // 数据库中字段
    	Company   Company // 不在数据库生成,只用来快速查询用
    }
    
    type Company struct {
    	ID   int
    	Name string
    }
    
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    	//db.AutoMigrate(&User{}) // Company也会被一起创建好,并设置了外键
    
    	// 方式一:插入数据,两个表都会被插入
    	//db.Create(&User{
    	//	Name:"lxx",
    	//	Company: Company{
    	//		ID: 1,
    	//		Name: "抖音",
    	//	},
    	//})
    	// 方式一:使用CompanyID插入
    	//db.Create(&User{
    	//	Name:"lxx2",
    	//	CompanyID: 1,
    	//})
    	// 方式三:使用Company插入,但ID存在了
    	db.Create(&User{
    		Name:"lxx3",
    		Company: Company{
    			ID:1,
    		},
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    十四 通过preload和joins查询多表

    package main
    
    import (
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	gorm.Model
    	Name      string
    	CompanyID int // 数据库中字段  默认必须为 结构体名+ID
    	Company   Company // 不在数据库生成,只用来快速查询用
    }
    
    type Company struct {
    	ID   int
    	Name string
    }
    
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    	// 1 普通查询
    	var user User
    	//db.First(&user)
    	//fmt.Println(user)
    	//fmt.Println(user.Company) // 空的,没有查出来,也就是默认并不会二次查询
    	
    	// 3 Preload预加载
    	//db.Preload("Company").First(&user)
    	//fmt.Println(user.Company)
    
    	// 4 Joins链表查
    	db.Joins("Company").First(&user)
    	fmt.Println(user.Company)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    十五 一对多关系

    package main
    
    import (
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	gorm.Model
    	CreditCards []CreditCard `gorm:"foreignkey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
    }
    
    type CreditCard struct {
    	gorm.Model
    	Number string
    	UserID uint
    }
    
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    	// 1 创建表
    	//db.AutoMigrate(&User{},&CreditCard{})
    
    	//2 插入数据
    	//var user=User{}
    	//db.Create(&user) // 插入user
    	//db.Create(&CreditCard{ // 插入CreditCard
    	//	Number:"1001",
    	//	UserID:user.ID,
    	//})
    	//db.Create(&CreditCard{ // 插入CreditCard
    	//	Number:"1002",
    	//	UserID:user.ID,
    	//})
    
    	// 3 查询
    	var user User
    	db.Preload("CreditCards").First(&user)
    	fmt.Println(user.CreditCards)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    十六 多对多的关系

    package main
    
    import (
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    // User 拥有并属于多种 language,`user_languages` 是连接表
    type User struct {
    	gorm.Model
    	Languages []Language `gorm:"many2many:user_languages;"`
    }
    
    type Language struct {
    	gorm.Model
    	Name string
    }
    
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    	// 1 创建表
    	//db.AutoMigrate(&User{}) //language表和中间表会自动创建
    
    	// 2 插入数据
    	//var lan =[]Language{}
    	//lan=append(lan,Language{
    	//	Name: "Go语言",
    	//})
    	//lan=append(lan,Language{
    	//	Name: "Python语言",
    	//})
    	//db.Create(&User{
    	//	Languages:lan,
    	//})
    
    	// 3 Preload查询数据
    	//var user User
    	//db.Preload("Languages").First(&user)
    	//fmt.Println(user.Languages)
    
    	// 4 没有使用Preload已经拿到User,再想获取Languages
    	var user User
    	db.First(&user)
    	fmt.Println(user.Languages)
    	var lan []Language
    	db.Model(&user).Association("Languages").Find(&lan)
    	fmt.Println(lan)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    十七 gorm其它操作

    17.1 自定义表名

    17.1.1 完全自定义表名
    // 给表结构体绑定方法即可
    func (user *User) TableName ()string {
    	return "lxx_user"
    }
    
    • 1
    • 2
    • 3
    • 4
    17.1.2 给所有表名增加前缀
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
      NamingStrategy: schema.NamingStrategy{
        TablePrefix: "t_",   // table name prefix, table for `User` would be `t_users`
        SingularTable: true, // use singular table name, table for `User` would be `user` with this option enabled
        NoLowerCase: true, // skip the snake_casing of names
        NameReplacer: strings.NewReplacer("CID", "Cid"), // use name replacer to change struct/field name before convert it to db name
      },
    })
    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"gorm.io/gorm/schema"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	gorm.Model
    	Languages []Language `gorm:"many2many:user_languages;"`
    }
    
    //func (user *User) TableName ()string {
    //	return "lxx_user"
    //}
    type Language struct {
    	gorm.Model
    	Name string
    }
    
    func main() {
    	// 日志配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢 SQL 阈值
    			LogLevel:                  logger.Info, // 日志级别为info
    			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
    			Colorful:                  true,        // 彩色打印
    		},
    	)
    
    	dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
        // 跟上面的方式冲突,优先用上面方式
    		NamingStrategy: schema.NamingStrategy{
    			TablePrefix: "lxx_",
    		},
    
    	})
    	if err != nil {
    		panic(err)
    	}
    	// 1 创建表
    	db.AutoMigrate(&User{})
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    17.2 自定义事件字段

    package main
    
    import (
       "gorm.io/driver/mysql"
       "gorm.io/gorm"
       "gorm.io/gorm/logger"
       "gorm.io/gorm/schema"
       "log"
       "os"
       "time"
    )
    
    type User struct {
       ID uint
       Name string
       AddTime time.Time
    }
    
    
    func main() {
       // 日志配置
       newLogger := logger.New(
          log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
          logger.Config{
             SlowThreshold:             time.Second, // 慢 SQL 阈值
             LogLevel:                  logger.Info, // 日志级别为info
             IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
             Colorful:                  true,        // 彩色打印
          },
       )
    
       dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
       db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
          Logger: newLogger,
          NamingStrategy: schema.NamingStrategy{
             TablePrefix: "lxx_",
          },
    
       })
       if err != nil {
          panic(err)
       }
       // 1 创建表
       //db.AutoMigrate(&User{})
       // 报错
       //db.Create(&User{
       // Name: "lxx",
       //})
    
       // 可以
       //db.Create(&User{
       // Name: "lxx",
       // AddTime: time.Now(),
       //})
    
       // 写了钩子后,不用手动写时间
       db.Create(&User{
          Name: "lxx2",
       })
    
    
    }
    // 编写钩子
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
       u.AddTime=time.Now()
       return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
  • 相关阅读:
    JS 数据结构:链表
    805. 数组的均值分割 : 折半搜索 + 二进制枚举运用题
    Promethus(普罗米修斯)安装与配置(亲测可用)
    Java 基于 SpringBoot 的酒店管理系统,附源码和数据库
    solidity开发讲解
    【postgres】备份还原数据库
    深入剖析CVE-2021-40444-Cabless利用链
    springboot整合mybatis
    AI视频下载:零基础2小时学会开发 Chrome扩展程序
    用Python操作Word文档,看这一篇就对了!
  • 原文地址:https://blog.csdn.net/qq_55752792/article/details/126695374