一文详解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  // 驱动

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) // 如果数据库不存在会报错

	}
}

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{})

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})
日志级别

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

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

Debug

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

db.Debug().Where("name = ?", "jinzhu").First(&User{})
具体代码
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{})
}

三 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{})
}

四 快速增删改查

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)  // 软删除
}

4.2 逻辑删除

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

五 零值问题

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表中好多数据都会被更新为空

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}}) 	

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}) // 仅更新非零值字段

六 表结构定义细节

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
}

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"`
}

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

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
}
创建/更新时间追踪(纳秒、毫秒、秒、Time)

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

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

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

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

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

type 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
}

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

type Author struct {
    Name  string
    Email string
}

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

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

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

声明 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{}) 
  // 如果表之前存在会修改,但是只会修改之前存在的字段的属性,无法识别删除字段
}

七 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{})

}

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)  // 返回插入记录的条数

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")

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

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')

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

使用 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)

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)

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
}

如果您想跳过 钩子 方法,您可以使用 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)

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},
	})

注意: 根据 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)"))

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` ...

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

db.Omit("CreditCard").Create(&user)

// 跳过所有关联
db.Omit(clause.Associations).Create(&user)
默认值

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

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

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

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

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

注意 若要数据库有默认、虚拟/生成的值,你必须为字段设置 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:(-);"`
}

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

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", ...;

您还可以查看 高级查询 中的 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))

如果你想避免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
用主键检索

如果主键是数字类型,您可以使用 内联条件 来检索对象。 传入字符串参数时,需要特别注意 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)

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

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

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

//当目标对象具有主键值时,主键将用于构建条件
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

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

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';
Struct & Map 条件
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

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

// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

注意 使用 struct 查询时,GORM 只会查询非零字段,这意味着如果您的字段的值为 0''false 或其他 [零值](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";

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

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

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

指定结构体查询字段

使用 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;
内联条件

查询条件可以以类似于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;
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;
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);

对于更复杂的 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;

另请查看 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)
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)

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

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)

8.8 Distinct

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

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

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

加入条件

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;

更多详情请参考预加载(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

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)

九 高级查询(了解)

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

注意 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`

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

查看 原生 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%")
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

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")

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

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

查看 原生 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)

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}

如果没有找到记录,可以使用包含更多的属性的结构体初始化 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}

不管是否找到记录,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}

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

如果没有找到记录,可以使用包含更多的属性的结构体创建记录,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}

不管是否找到记录,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}

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`

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

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`)"

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

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)

  // 业务逻辑...
}

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 // 整个批量操作影响的记录数

9.13 查询钩子

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

func (u *User) AfterFind(tx *gorm.DB) (err error) {
  if u.Role == "" {
    u.Role = "user"
  }
  return
}

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)

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 且已付款或已发货的订单

查看 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

十 更新

// 表模型变成
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)

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;

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;

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;

注意 当通过 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})

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
}

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)
阻止全局更新

如果在没有任何条件的情况下执行批量更新,默认情况下,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"
更新的记录数

获取受更新影响的行数

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

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

并且 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
根据子查询进行更新

使用子查询更新表

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'
不使用 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;
检查字段是否有变更?

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})
在 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
}

十一 删除

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

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})

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
}

11.4 批量删除

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

db.Where("name LIKE ?", "%lxx%").Delete(&User{})

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

如果在没有任何条件的情况下执行批量删除,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
返回删除行的数据(了解)

返回被删除的数据,仅适用于支持 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}}

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;

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

type User struct {
  ID      int
  Deleted gorm.DeletedAt
  Name    string
}
查找被软删除的记录

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

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

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

var users []User
db.Unscoped().Where("age = 99").Find(&users)
db.Unscoped().Delete(&users)
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;

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

十二 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)

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")

注意 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"

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}

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

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)

获取 *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)
}

转到 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)
}

12.7 Connection

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

db.Connection(func(tx *gorm.DB) error {
  tx.Exec("SET my.role = ?", "admin")

  tx.First(&User{})
})

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},
}

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

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

生成SQL:

SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 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

支持是因为 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...);
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`

十三 关联之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{})

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

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
}

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
}

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的设计表中可以查看

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,
		},
	})
}

十四 通过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)
}

十五 一对多关系

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)

}

十六 多对多的关系

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)

}

十七 gorm其它操作

17.1 自定义表名

17.1.1 完全自定义表名
// 给表结构体绑定方法即可
func (user *User) TableName ()string {
	return "lxx_user"
}
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{})

}

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
}
  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go&Python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值