gorm的简单操作

1. 什么是orm

ORM全称是:Object Relational Mapping(对象关系映射),其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。举例来说就是,我定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。
对于数据来说,最重要最常用的是表:表中有列, orm就是将一张表映射成一个类,表中的列映射成类中的一个类。java 、python,但是针对go语言而言,struct,就是列如何映射,是因为列可以映射成struct中的类型,int->int,但是有另一个问题? 就是数据库中的列具备很好的描述性,但是struct有tag(标签)。执行sql, 需要我们有足够的sql语句基础、需要我们懂得不同的数据的sql

2. 常用orm

个人而言,不用太去纠结应该选择哪一个orm框架,但是实际上你用熟悉了一个,其他的orm迁移成本很低,我们选个一个star数量最高的一定不会有错,这些差异也不会很大sql语言远比orm重要的多
https://github.com/go-gorm/gorm
https://github.com/facebook/ent
https://github.com/jmoiron/sqlx
https://gitea.com/xorm/xorm/src/branch/master/README_CN.md
https://github.com/didi/gendry/blob/master/translation/zhcn/README.md

gorm文档

https://gorm.io/zh_CN/docs/create.html

3. orm的优缺点

优点:

  1. 提高了开发效率。
  2. 屏蔽sql细节。可以自动对实体Entity对象与数据库中的Table进行字段与属性的映射;不用直接SQL编码
  3. 屏蔽各种数据库之间的差异

缺点:

  1. orm会牺牲程序的执行效率和会固定思维模式
  2. 太过依赖orm会导致sql理解不够
  3. 对于固定的orm依赖过重,导致切换到其他的orm代价高

4. 如何正确看待orm和sql之间的关系

  1. sql为主,orm为辅
  2. orm主要目的是为了增加代码可维护性和开发效率

gorm连接数据库

package mainimport ("gorm.io/driver/mysql""gorm.io/gorm"
)func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic(err)}}

注意:想要正确的处理 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 nameDefaultStringSize: 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{})

//设置全局的logger,这个logger在我们执行每个sql语句的时候会打印每一行sql

日志

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

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

newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:              time.Second,   // Slow SQL thresholdLogLevel:                   logger.Silent, // Log levelIgnoreRecordNotFoundError: true,           // Ignore ErrRecordNotFound error for loggerParameterizedQueries:      true,           // Don't include params in the SQL logColorful:                  false,          // Disable color},
)// Globally mode
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{Logger: newLogger,
})// Continuous session mode
tx := db.Session(&Session{Logger: newLogger})
tx.First(&user)
tx.Model(&user).Update("Age", 18)

日志级别

GORM 定义了这些日志级别:Silent、Error、Warn、Info

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent),
})

Debug

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

db.Debug().Where("name = ?", "jinzhu").First(&User{})

自定义 Logger

参考 GORM 的 默认 logger 来定义您自己的 logger

Logger 需要实现以下接口,它接受 context,所以你可以用它来追踪日志

type Interface interface {LogMode(LogLevel) InterfaceInfo(context.Context, string, ...interface{})Warn(context.Context, string, ...interface{})Error(context.Context, string, ...interface{})Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)
}

创建表

_ = db.AutoMigrate(&Product{}) //此次应该有sql语句跟日志级别有关系

通过NullString解决不能更新零值的问题
整体源码

package mainimport ("database/sql""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""log""os""time"
)type Product struct {gorm.Model//Code  stringCode  sql.NullStringPrice uint
}func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold: time.Second, // 慢SQL阈值LogLevel:      logger.Info, // 日志级别// 忽略记录器的ErrRecordNotFound错误 是否忽略 record not found 错误,默认为 false,如果设置为 true,则在查询结果为空时不会打印 record not found 错误信息IgnoreRecordNotFoundError: false,ParameterizedQueries:      false, // 不要在SQL日志中包含参数Colorful:                  true,  // 禁言彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger,})if err != nil {panic(err)}//设置全局的logger,这个logger在我们执行每个sql语句的时候会打印每一行sql//定义一个表结构,将表结构直接生成对应的表---migrations(迁移)//迁移_ = db.AutoMigrate(&Product{}) //此次应该有sql语句// 新增//db.Create(&Product{Code: "D42", Price: 100})db.Create(&Product{Code: sql.NullString{"D42", true}, Price: 100})// 查询var product Productdb.First(&product, 1) // 根据整型主键查找db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录// 更新一个值 - 将 product 的 price 更新为 200db.Model(&product).Update("Price", 200)// Update - 更新多个字段//db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段db.Model(&product).Updates(Product{Price: 200, Code: sql.NullString{String: "", Valid: true}}) // 仅更新非零值字段//db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})// Delete - 删除 product//并没有执行deleta语句,而是逻辑删除//db.Delete(&product, 1)
}

解决仅更新非零值字段的方法有两种

	/**1 将string设置为*string2 使用sql的NULLxxx来结局*/empty := ""db.Model(&User{ID: 1}).Updates(User{Email: &empty})//updates语句不会更新零值,但update语句会更新//db.Model(&User{ID: 1}).Update("Name", "")//db.Model(&User{ID: 1}).Updates(User{Name: ""})

约定

GORM 倾向于约定优于配置 默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间
如果您遵循 GORM 的约定,您就可以少写的配置、代码。 如果约定不符合您的实际要求,GORM 允许你配置它们

嵌入结构体

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

type User struct {gorm.ModelName string
}
// 等效于
type User struct {ID        uint           `gorm:"primaryKey"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt gorm.DeletedAt `gorm:"index"`Name string
}

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

type Author struct {Name  stringEmail string
}type Blog struct {ID      intAuthor  Author `gorm:"embedded"`Upvotes int32
}
// 等效于
type Blog struct {ID    int64Name  stringEmail stringUpvotes  int32
}

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

type Blog struct {ID      intAuthor  Author `gorm:"embedded;embeddedPrefix:author_"`Upvotes int32
}
// 等效于
type Blog struct {ID          int64AuthorName stringAuthorEmail stringUpvotes     int32
}

字段标签

声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格–>去官网看源码

package mainimport ("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(50);index:idx_user_name;unique;default:'bobby'"`
}func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:             time.Second, // 慢SQL阈值LogLevel:                  logger.Info, // 日志级别IgnoreRecordNotFoundError: false,ParameterizedQueries:      false, // 不要在SQL日志中包含参数Colorful:                  true,  // 禁言彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger,})if err != nil {panic(err)}_ = db.AutoMigrate(&User{}) //此次应该有sql语句db.Create(&User{})}

在这里插入图片描述

通过create方法插入记录

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

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

users := []*User{User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},User{Name: "Jackson", Age: 19, Birthday: time.Now()},
}result := db.Create(users) // 传递切片以插入多行数据result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数
package mainimport ("database/sql""fmt""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""log""os""time"
)type User struct {ID           uintName         stringEmail        *string //使用指针的方法解决空字符串Age          uint8Birthday     *time.TimeMemberNumber sql.NullStringActivatedAt  sql.NullTimeCreatedAt    time.TimeUpdatedAt    time.Time
}func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:             time.Second, // 慢SQL阈值LogLevel:                  logger.Info, // 日志级别IgnoreRecordNotFoundError: false,ParameterizedQueries:      false, // 不要在SQL日志中包含参数Colorful:                  true,  // 禁言彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger,})if err != nil {panic(err)}_ = db.AutoMigrate(&User{}) //此次应该有sql语句user := User{Name: "bobby2",}//user.ID             // 返回插入数据的主键//result.Error        // 返回 error//result.RowsAffected // 返回插入记录的条数fmt.Println(user.ID)result := db.Create(&user)fmt.Println(user.ID)fmt.Println(result.Error)fmt.Println(result.RowsAffected)//updates语句不会更新零值,但update语句会更新//db.Model(&User{ID: 1}).Update("Name", "")//db.Model(&User{ID: 1}).Updates(User{Name: ""})//解决仅更新非零值字段的方法有两种/**1 将string设置为*string2 使用sql的NULLxxx来结局*///empty := ""//db.Model(&User{ID: 1}).Updates(User{Email: &empty})}

用指定的字段创建记录

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

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

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

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

批量插入

要高效地插入大量记录,请将切片传递给Create方法。GORM将生成一条SQL语句来插入所有数据并回填主键值,钩子方法也将被调用。. 当记录可以分成多个批处理时,它将开始一个 交易。

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

你可以在使用CreateInBatches创建时指定批处理大小,例如:

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

为什么不一次性提交所有的,还要分批次?
答:sql语句是有长度限制!

在使用Upsert(插入更新)和Create With Associations(使用关联创建)时,也支持批量插入
初始化GORM时使用CreateBatchSize选项,所有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)

创建钩子

GORM允许用户定义钩子来实现前保存,前创建,后保存,后创建。这些钩子方法将在创建记录时被调用,有关生命周期的详细信息请参阅Hooks

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {u.UUID = uuid.New()if u.Role == "admin" {return errors.New("invalid role")}return
}

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

根据 Map 创建

GORM支持从map[string]interface{}和[]map[string]interface{}创建,例如:

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

当创建from map时,钩子不会被调用,关联不会被保存,主键值不会被回填

关联创建—>这个很像一对一

当创建一些具有关联的数据时,如果它的关联值不是零值,那么这些关联将被替换,并且它的Hooks方法将被调用。

type CreditCard struct {gorm.ModelNumber   stringUserID   uint
}type User struct {gorm.ModelName       stringCreditCard CreditCard
}db.Create(&User{Name: "jinzhu",CreditCard: CreditCard{Number: "411111111111"}
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...

你可以使用Select, Omit跳过保存关联,例如:

db.Omit("CreditCard").Create(&user)
// skip all associations
db.Omit(clause.Associations).Create(&user)

查询

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

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil// 检查 ErrRecordNotFound 错误===>没有找到数据
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user),Find方法可以接受struct和slice的数据。
对单个对象使用Find而不带limit,db.Find(&user)将会查询整个表并且只返回第一个对象,这是性能不高并且不确定的。

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

var user User
var users []User// works because destination struct is passed in
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1// works because model is specified using `db.Model()`
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1// doesn't work
result := map[string]interface{}{}
db.Table("users").First(&result)// works with Take
result := map[string]interface{}{}
db.Table("users").Take(&result)// no primary key defined, results will be ordered by first field (i.e., `Code`)
type Language struct {Code stringName string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
//通过first查询单个数据
var user User
db.First(&user)//通过主键查询
//我们不能给user赋值
result := db.First(&user, 2)if errors.Is(result.Error, gorm.ErrRecordNotFound) {fmt.Println("未找到")
}
fmt.Println(user.ID)var users []User
result1 := db.Find(&users)
fmt.Println("总共记录", result1.RowsAffected)for _, user := range users {fmt.Println(user.ID)
}

根据主键检索

如果主键是数字类型,您可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免SQL注入;查看 Security 部分来了解详情。

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);
//通过first查询单个数据var user Userdb.First(&user)//通过主键查询//我们不能给user赋值result := db.First(&user, 2)if errors.Is(result.Error, gorm.ErrRecordNotFound) {fmt.Println("未找到")}fmt.Println(user.ID)

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

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

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

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

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

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

检索全部对象

// Get all records
result := db.Find(&users)
// SELECT * FROM users;result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error
var users []Userresult1 := db.Find(&users)fmt.Println("总共记录", result1.RowsAffected)for _, user := range users {fmt.Println(user.ID)

条件

String 条件

// 获取第一条匹配记录
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;// 获取所有匹配的记录--->这个获取的所有匹配的记录之外的
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';// IN===>jinzhu或者jinzhu2
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

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

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

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

Struct & Map 条件

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

当使用struct查询时,GORM只会查询非零字段,这意味着如果字段的值为0,",false或其他零值,它将不会用于构建查询条件,例如:

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

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

db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
//通过where查询var user Userdb.Where("name = ?", "bobby1").First(&user)var users []Userdb.Where(&User{Name: "bobby1"}).Find(&users) //这个才是查询所有的for _, user1 := range users {fmt.Println(user1.ID)}db.Where("name IN ?", []string{"bobby1", "bobby2"}).Find(&users)

更新

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

db.First(&user)user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

Save是一个组合功能。如果save value不包含主键,它将执行Create,否则将执行Update(包含所有字段)。

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

更新单个列

当使用Update更新单个列时,它需要有任何条件,否则会引发错误ErrMissingWhereClause,请查看Block Global Updates了解详细信息。当使用Model方法并且它的值有一个主值时,主键将被用来构建条件,例如:

// Update with conditions
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's ID is `111`:
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;// Update with conditions and model value
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

更新多列

Updates支持使用struct或map[string]接口{}进行更新,当使用struct进行更新时,默认情况下只更新非零字段

// Update attributes with `struct`, will only update non-zero fields
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;// Update attributes with `map`
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

当使用struct进行更新时,GORM只会更新非零字段。您可能希望使用map来更新属性,或者使用Select来指定要更新的字段

更新选定字段

如果要更新选定的字段或在更新时忽略某些字段,可以使用Select, Omit

// Select with Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;// Select with Struct (select zero value fields)
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 all fields (select all fields include zero value fields)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})// Select all fields but omit Role (select all fields include zero value fields)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

更新 Hook

GORM允许钩子beforeave, BeforeUpdate, AfterSave, AfterUpdate。这些方法将在更新记录时调用,详情请参考Hooks

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

批量更新

如果我们没有使用Model指定具有主键值的记录,GORM将执行批处理更新

// Update with struct
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';// Update with map
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

阻止全局更新

如果你执行一个批处理更新没有任何条件,GORM将不会运行它,并将返回ErrMissingWhereClause错误默认
例如,您必须使用某些条件或使用原始SQL或启用AllowGlobalUpdate模式

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

更新的记录数

获取受更新影响的行数

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

删除

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

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

根据主键删除

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

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

钩子函数

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

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

批量删除

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

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

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

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

阻止全局删除

当你试图执行不带任何条件的批量删除时,GORM将不会运行并返回ErrMissingWhereClause 错误,如果一定要这么做,你必须添加一些条件,或者使用原生SQL,或者开启AllowGlobalUpdate 模式,如下例:

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

返回删除行的数据

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

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

软删除

如果你的模型包含了 gorm.DeletedAt字段(该字段也被包含在gorm.Model中),那么该模型将会自动获得软删除的能力!
当调用Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时间,而后的一般查询方法将无法查找到此条记录。

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

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

type User struct {ID      intDeleted gorm.DeletedAtName    string
}

查找被软删除的记录

你可以使用Unscoped来查询到被软删除的记录

db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

永久删除

你可以使用 Unscoped来永久删除匹配的记录

db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;

删除标志

默认情况下,gorm.Model使用*time.Time作为DeletedAt 的字段类型,不过软删除插件gorm.io/plugin/soft_delete同时也提供其他的数据格式支持
提示 当使用DeletedAt创建唯一复合索引时,你必须使用其他的数据类型,例如通过gorm.io/plugin/soft_delete插件将字段类型定义为unix时间戳等等

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

Unix 时间戳

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

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

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

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

使用 1 / 0 作为 删除标志

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

混合模式

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

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

Belongs To

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

// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {gorm.ModelName      stringCompanyID intCompany   Company
}type Company struct {ID   intName string
}

重写外键

要定义一个 belongs to 关系,数据库的表中必须存在外键。默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字
例如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID。
GORM同时提供自定义外键名字的方式,如下例所示。

type User struct {gorm.ModelName         stringCompanyRefer intCompany      Company `gorm:"foreignKey:CompanyRefer"`// 使用 CompanyRefer 作为外键
}type Company struct {ID   intName string
}

重写引用

对于 belongs to 关系,GORM 通常使用数据库表,主表(拥有者)的主键值作为外键参考。 正如上面的例子,我们使用主表Company中的主键字段ID作为外键的参考值。
如果设置了User实体属于Company实体,那么GORM会自动把Company中的ID属性保存到User的CompanyID属性中。
同样的,您也可以使用标签 references 来更改它,例如:

type User struct {gorm.ModelName      stringCompanyID stringCompany   Company `gorm:"references:Code"` // 使用 Code 作为引用
}type Company struct {ID   intCode stringName string
}

NOTE 如果外键名恰好在拥有者类型中存在,GORM 通常会错误的认为它是 has one 关系。我们需要在 belongs to 关系中指定 references

type User struct {gorm.ModelName      stringCompanyID stringCompany   Company `gorm:"references:CompanyID"` // 使用 Company.CompanyID 作为引用
}type Company struct {CompanyID   intCode        stringName        string
}
package mainimport ("gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""log""os""time"
)type User struct {gorm.ModelName      stringCompanyID int //数据库中存储的字段  sql使用它Company   Company
}type Company struct {ID   intName string
}func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:             time.Second, // 慢SQL阈值LogLevel:                  logger.Info, // 日志级别IgnoreRecordNotFoundError: false,ParameterizedQueries:      false, // 不要在SQL日志中包含参数Colorful:                  true,  // 禁言彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger,})if err != nil {panic(err)}//db.AutoMigrate(&User{}) //新建了user表和company表,并且设置外键//db.Create(&User{//	Name: "bobby",//	Company: Company{//		Name: "chengpeng",//	},//})//解决外键存在的问题db.Create(&User{Name: "bobby2",Company: Company{ID: 1,},})
}

Belongs to 的 CRUD

查看 关联模式 获取 belongs to 相关的用法

预加载

GORM 可以通过 Preload、Joins 预加载 belongs to 关联的记录,查看 预加载 获取详情

外键约束

你可以通过 constraint 标签配置 OnUpdate、OnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

type User struct {gorm.ModelName      stringCompanyID intCompany   Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}type Company struct {ID   intName string
}
var user User
//Preload
//db.Preload("Company").First(&user)
db.Joins("Company").First(&user)
fmt.Println(user)

Has Many

has many 与另一个模型建立了一对多的连接。 不同于 has one,拥有者可以有零或多个关联模型。

例如,您的应用包含 user 和 credit card 模型,且每个 user 可以有多张 credit card。

声明

// User 有多张 CreditCard,UserID 是外键
type User struct {gorm.ModelCreditCards []CreditCard
}type CreditCard struct {gorm.ModelNumber stringUserID uint
}

检索

// 检索用户列表并预加载信用卡
func GetAll(db *gorm.DB) ([]User, error) {var users []Usererr := db.Model(&User{}).Preload("CreditCards").Find(&users).Errorreturn users, err
}

重写外键

要定义 has many 关系,同样必须存在外键。 默认的外键名是拥有者的类型名加上其主键字段名
例如,要定义一个属于 User 的模型,则其外键应该是 UserID。
此外,想要使用另一个字段作为外键,您可以使用 foreignKey 标签自定义它:

type User struct {gorm.ModelCreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}type CreditCard struct {gorm.ModelNumber    stringUserRefer uint
}

重写引用

GORM 通常使用拥有者的主键作为外键的值。 对于上面的例子,它是 User 的 ID 字段。
为 user 添加 credit card 时,GORM 会将 user 的 ID 字段保存到 credit card 的 UserID 字段。
同样的,您也可以使用标签 references 来更改它,例如:

type User struct {gorm.ModelMemberNumber stringCreditCards  []CreditCard `gorm:"foreignKey:UserNumber;references:MemberNumber"`
}type CreditCard struct {gorm.ModelNumber     stringUserNumber string
}

多态关联

GORM 为 has one 和 has many 提供了多态关联支持,它会将拥有者实体的表名、主键都保存到多态类型的字段中。

type Dog struct {ID   intName stringToys []Toy `gorm:"polymorphic:Owner;"`
}type Toy struct {ID        intName      stringOwnerID   intOwnerType string
}db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs"), ("toy2","1","dogs")

您可以使用标签 polymorphicValue 来更改多态类型的值,例如:

type Dog struct {ID   intName stringToys []Toy `gorm:"polymorphic:Owner;polymorphicValue:master"`
}type Toy struct {ID        intName      stringOwnerID   intOwnerType string
}db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","master"), ("toy2","1","master")

Has Many 的 CURD

查看 关联模式 获取 has many 相关的用法

预加载

GORM 可以通过 Preload 预加载 has many 关联的记录,查看 预加载 获取详情

自引用 Has Many

type User struct {gorm.ModelName      stringManagerID *uintTeam      []User `gorm:"foreignkey:ManagerID"`
}

外键约束

你可以通过为标签 constraint 配置 OnUpdate、OnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

type User struct {gorm.ModelCreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}type CreditCard struct {gorm.ModelNumber stringUserID uint
}

你也可以在删除记录时通过 Select 来删除 has many 关联的记录,查看 Delete with Select 获取详情

package mainimport ("fmt""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""log""os""time"
)// User 有多张 CreditCard,UserID 是外键
type User struct {gorm.ModelCreditCards []CreditCard `gorm:"foreignKey:UserID"`
}type CreditCard struct {gorm.ModelNumber stringUserID uint
}func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:             time.Second, // 慢SQL阈值LogLevel:                  logger.Info, // 日志级别IgnoreRecordNotFoundError: false,ParameterizedQueries:      false, // 不要在SQL日志中包含参数Colorful:                  true,  // 禁言彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger,})if err != nil {panic(err)}//db.AutoMigrate(&User{}) //如果一个一个创建,就会发现没有外键//db.AutoMigrate(&CreditCard{})//db.AutoMigrate(&User{}, &CreditCard{}) //这样创建就会有外键//user := &User{}//db.Create(&user)//db.Create(&CreditCard{//	Number: "123",//	UserID: 1,//})////db.Create(&CreditCard{//	Number: "1234",//	UserID: 1,//})var user Userdb.Preload("CreditCards").First(&user)for _, card := range user.CreditCards {fmt.Println(card.Number)}
}

在大型的系统中,不建议使用外键约束,外键约束也有很大的优点:数据的完整性
外键约束会让你的数据很完整,即使是业务代码,有些人考虑的不严谨也不会造成数据不一致
在大型的系统,高并发的系统重一般不使用外键约束,自己在业务层面保证数据的一致性

Many To Many

Many to Many 会在两个 model 中添加一张连接表。
例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。

// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {gorm.ModelLanguages []Language `gorm:"many2many:user_languages;"`
}type Language struct {gorm.ModelName string
}

当使用 GORM 的 AutoMigrate 为 User 创建表时,GORM 会自动创建连接表

反向引用

声明

// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {gorm.ModelLanguages []*Language `gorm:"many2many:user_languages;"`
}type Language struct {gorm.ModelName stringUsers []*User `gorm:"many2many:user_languages;"`
}

检索

// 检索 User 列表并预加载 Language
func GetAllUsers(db *gorm.DB) ([]User, error) {var users []Usererr := db.Model(&User{}).Preload("Languages").Find(&users).Errorreturn users, err
}// 检索 Language 列表并预加载 User
func GetAllLanguages(db *gorm.DB) ([]Language, error) {var languages []Languageerr := db.Model(&Language{}).Preload("Users").Find(&languages).Errorreturn languages, err
}

重写外键

对于 many2many 关系,连接表会同时拥有两个模型的外键,例如:

type User struct {gorm.ModelLanguages []Language `gorm:"many2many:user_languages;"`
}type Language struct {gorm.ModelName string
}// 连接表:user_languages
//   foreign key: user_id, reference: users.id
//   foreign key: language_id, reference: languages.id

若要重写它们,可以使用标签 foreignKey、references、joinforeignKey、joinReferences。当然,您不需要使用全部的标签,你可以仅使用其中的一个重写部分的外键、引用。

type User struct {gorm.ModelProfiles []Profile `gorm:"many2many:user_profiles;foreignKey:Refer;joinForeignKey:UserReferID;References:UserRefer;joinReferences:ProfileRefer"`Refer    uint      `gorm:"index:,unique"`
}type Profile struct {gorm.ModelName      stringUserRefer uint `gorm:"index:,unique"`
}// 会创建连接表:user_profiles
//   foreign key: user_refer_id, reference: users.refer
//   foreign key: profile_refer, reference: profiles.user_refer

注意: 某些数据库只允许在唯一索引字段上创建外键,如果您在迁移时会创建外键,则需要指定 unique index 标签。

自引用 Many2Many

自引用 many2many 关系

type User struct {gorm.ModelFriends []*User `gorm:"many2many:user_friends"`
}// 会创建连接表:user_friends
//   foreign key: user_id, reference: users.id
//   foreign key: friend_id, reference: users.id

预加载

GORM 可以通过 Preload 预加载 has many 关联的记录,查看 预加载 获取详情

Many2Many 的 CURD

查看 关联模式 获取 many2many 相关的用法

自定义连接表

JoinTable可以是一个全功能的模型,像有软删除,钩子支持和更多的字段,你可以设置它与SetupJoinTable,例如:
注意: 自定义连接表要求外键是复合主键或复合唯一索引

type Person struct {ID        intName      stringAddresses []Address `gorm:"many2many:person_addressses;"`
}type Address struct {ID   uintName string
}type PersonAddress struct {PersonID  int `gorm:"primaryKey"`AddressID int `gorm:"primaryKey"`CreatedAt time.TimeDeletedAt gorm.DeletedAt
}func (PersonAddress) BeforeCreate(db *gorm.DB) error {// ...
}// 修改 Person 的 Addresses 字段的连接表为 PersonAddress
// PersonAddress 必须定义好所需的外键,否则会报错
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

外键约束

你可以通过为标签 constraint 配置 OnUpdate、OnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

type User struct {gorm.ModelLanguages []Language `gorm:"many2many:user_speaks;"`
}type Language struct {Code string `gorm:"primarykey"`Name string
}// CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user

你也可以在删除记录时通过 Select 来删除 many2many 关系的记录,查看 Delete with Select 获取详情

复合外键

如果您的模型使用了 复合主键,GORM 会默认启用复合外键。

您也可以覆盖默认的外键、指定多个外键,只需用逗号分隔那些键名,例如:

type Tag struct {ID     uint   `gorm:"primaryKey"`Locale string `gorm:"primaryKey"`Value  string
}type Blog struct {ID         uint   `gorm:"primaryKey"`Locale     string `gorm:"primaryKey"`Subject    stringBody       stringTags       []Tag `gorm:"many2many:blog_tags;"`LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;References:id"`SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;References:id"`
}// 连接表:blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: blog_locale, reference: blogs.locale
//   foreign key: tag_id, reference: tags.id
//   foreign key: tag_locale, reference: tags.locale// 连接表:locale_blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: blog_locale, reference: blogs.locale
//   foreign key: tag_id, reference: tags.id// 连接表:shared_blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: tag_id, reference: tags.id
package mainimport ("fmt""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""log""os""time"
)type User struct {gorm.ModelLanguages []Language `gorm:"many2many:user_languages;"`
}type Language struct {gorm.ModelName string
}func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:             time.Second, // 慢SQL阈值LogLevel:                  logger.Info, // 日志级别IgnoreRecordNotFoundError: false,ParameterizedQueries:      false, // 不要在SQL日志中包含参数Colorful:                  true,  // 禁言彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger,})if err != nil {panic(err)}//db.AutoMigrate(&User{})//插入//languages := []Language{}//languages = append(languages, Language{Name: "go"})//languages = append(languages, Language{Name: "java"})//user := User{////	Languages: languages,//}////db.Create(&user)//获取数据--第一种//var user User//db.Preload("Languages").First(&user)//for _, language := range user.Languages {//	fmt.Println(language.Name)//}//如果我们已经取出一个用户来了,但是这个用户我们之前没有使用preload来加载对应的Languages//不是说用户有language我们就一定要取出来// 开始关联模式//查找关联-->第二种方法var user Userdb.First(&user)var laguages []Language_ = db.Model(&user).Association("Languages").Find(&laguages)for _, laguage := range laguages {fmt.Println(laguage.Name)}fmt.Println(user.ID)}

我们自己定义表面是什么
同一的给所有的表面加上前缀
NamingStrategy和TableName不能同时配置

package mainimport ("gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""gorm.io/gorm/schema""log""os""time"
)//type Language1 struct {
//	gorm.Model
//	Name string
//}type Language2 struct {Name    stringAddTime time.Time //每个记录创建的时候自动加上当前加入到AddTime中  不加BeforeCreate这个方法就会报错//也可以加这样加//AddTime1 sql.NullTime
}func (l *Language2) BeforeCreate(tx *gorm.DB) (err error) {l.AddTime = time.Now()return
}//在数据默认是加一个默认值// TableName 在gorm中可以通过给某一个struct添加TableName方法自定义表面
// 自定义表名
//func (Language1) TableName() string {
//	return "my_language"
//}// 同一个表名加一个前缀
func main() {// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情//loc 本地时区//想要正确的处理 time.Time ,您需要带上 parseTime 参数dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"//NamingStrategy和TableName不能同时配置newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:             time.Second, // 慢SQL阈值LogLevel:                  logger.Info, // 日志级别IgnoreRecordNotFoundError: false,ParameterizedQueries:      false, // 不要在SQL日志中包含参数Colorful:                  true,  // 禁言彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{NamingStrategy: schema.NamingStrategy{TablePrefix: "chengpeng_",},Logger: newLogger,})if err != nil {panic(err)}db.AutoMigrate(&Language2{})db.Create(&Language2{Name: "Python"})
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/146637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无需添加udid,ios企业证书的自助生成方法

我们开发uniapp的app的时候&#xff0c;需要苹果证书去打包。 假如申请的是个人或company类型的苹果开发者账号&#xff0c;必须上架才能安装&#xff0c;异常的麻烦&#xff0c;但是有一些app&#xff0c;比如企业内部使用的app&#xff0c;是不需要上架苹果应用市场的。 假…

Redis篇---第六篇

系列文章目录 文章目录 系列文章目录前言一、Redis 为什么设计成单线程的?二、什么是 bigkey?会存在什么影响?三、熟悉哪些 Redis 集群模式?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,…

MySQL存储架构

连接管理与安全性 每个客户端连接都会在服务器进程中拥有一个线程&#xff0c;这个连接的查询只会在这个线程中执行。MySQL5.5以后支持了一个API叫线程池插件&#xff0c;可以用少量线程服务大量连接&#xff0c;因此不用每次都新建连接然后销毁。 客户端连接MySQL服务器时候&…

酷柚易汛ERP - 系统初始化操作指南

1、应用场景 重新初始化将会清空系统数据&#xff0c;恢复至新系统初始创建的状态&#xff0c;且不可逆转&#xff0c;请谨慎操作&#xff01; 初始化系统需要验证管理员密码&#xff0c;密码输入正确后即可开始初始化系统

使用maven命令打包依赖

1、maven仓库地址 阿里云地址&#xff1a;https://developer.aliyun.com/mvn/search 中央仓库地址&#xff1a;https://mvnrepository.com/ 2、下载方式 在地址栏中输入要搜索的依赖 选择需要的版本 &#xff08;1&#xff09;直接复制 &#xff08;2&#xff09;pom下载 …

4.5 Windows驱动开发:实现进程数据转储

多数ARK反内核工具中都存在驱动级别的内存转存功能&#xff0c;该功能可以将应用层中运行进程的内存镜像转存到特定目录下&#xff0c;内存转存功能在应对加壳程序的分析尤为重要&#xff0c;当进程在内存中解码后&#xff0c;我们可以很容易的将内存镜像导出&#xff0c;从而更…

汽车 CAN\CANFD数据记录仪

CAN FD数据记录仪解决汽车电子数据记录与偶发性故障查找问题。 1、脱机离线记录两路CAN/CANFD通道数据 脱机离线记录两路CAN/CANFD通道数据&#xff0c;可记录6个月数据。每个通 道单独设置触发记录模式、触发前预记录报文个数&#xff08;默认1000帧&#xff09;及 过滤规则&a…

JavaScript 浮点数运算的精度问题及解决

JavaScript 浮点数运算的精度问题及解决 在 JavaScript 中整数和浮点数都属于 Number 数据类型&#xff0c;当浮点数做数学运算的时候&#xff0c;你经常会发现一些问题&#xff0c;举几个例子&#xff1a; 0.1 0.2 0.30000000000000004 console.log(0.1 0.2) 0.3000000…

C# Socket通信从入门到精通(9)——如何设置本机Ip地址

前言&#xff1a; 我们开发好Socket通信程序以后&#xff0c;上机调试的时候&#xff0c;首先要做的就是先设置好电脑的IP&#xff0c;这样才能实现不同的电脑之间的通信&#xff0c;并且电脑1的ip地址和电脑2的Ip地址要同处于一个网段&#xff0c;比如电脑1的Ip地址为192.168…

《少儿编程启蒙指南》

《少儿编程启蒙指南》大纲 本文详细阐述少儿编程启蒙&#xff0c;如果有人喜欢&#xff0c;往后我会继续更新迭代此文。 “Everyone should know how to program a computer, because it teaches you how to think.”—Steve Jobs 每个人都应该知道如何编程&#xff0c;因为它…

VS 将 localhost访问改为ip访问

项目场景&#xff1a; 使用vs进行本地调试时需要多人访问界面,使用ip访问报错 问题描述 vs通过ip访问报错 虚拟机或其它电脑不能正常打开 原因分析&#xff1a; 原因是vs访问规则默认是iis,固定默认启动地址是localhost 解决方案&#xff1a; 1.vs项目启动之后会出现这个 右…

flutter开发web应用支持浏览器跨域设置

开发web应用难免会遇到跨域问题&#xff0c;所以flutter设置允许web跨域的设置是要在你的flutter安装路径下面 flutter\bin\cache 找到flutter_tools.stamp文件&#xff0c;然后删除掉&#xff1a;这个文件是临时缓存文件 然后找到 flutter\packages\flutter_tools\lib\src\web…

Linux脚本shell中将Windos格式字符转换为unix

众所周知&#xff0c;windos的文档直接复制到linux服务器上去&#xff0c;是需要进行格式转换的&#xff0c;否则可能出现以下报错&#xff1a; 解决方法&#xff1a; vim 脚本 输入 :set ff ##会显示字符格式 :set ffunix ##转换为unix格式 :wq ##保存退出

【Linux】第十八站:进程等待

文章目录 一、进程等待的必要性1.进程等待是什么2.进程等待的必要性3.为什么要进程等待呢&#xff1f; 二、进程等待的方法1.问题2.wait3.waitpid4.status的原理5.等待失败6.与status有关的两个宏7.options 一、进程等待的必要性 1.进程等待是什么 通过系统调用wait/waitpid&a…

微服务基础,分布式核心,常见微服务框架,SpringCloud概述,搭建SpringCloud微服务项目详细步骤,含源代码

微服务基础 系统架构的演变 随着会联网的发展&#xff0c;网站应用的规模不断扩大&#xff0c;常规的应用架构已经无法应对&#xff0c;分布式服务架构以及微服务架构势在必行&#xff0c;必须一个治理系统确保架构有条不紊的演进 单体应用框架 Web应用程序发展的早期&…

PyCharm 远程连接服务器并使用服务器的 Jupyter 环境

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

我所理解的 UI Toolkit 启蒙阶段(一)

我所理解的 UI Toolkit 启蒙阶段&#xff08;一&#xff09; 对于自己不会的新东西的学习&#xff0c;我认为最合适的路径就是&#xff1a; 实例教学视频 —> 实操熟悉 —> 官方文档查漏补缺 —> 拟定思路实现功能 但这 4 步并非每一步都需要下 100% 的功夫&#x…

虚拟机上安装docker,并安装flink镜像

1. 安装docker 官网步骤&#xff1a;https://docs.docker.com/engine/install/centos/ sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.…

2024年山东省职业院校技能大赛中职组“网络安全”赛项竞赛试题-A

2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-A 一、竞赛时间 总计&#xff1a;360分钟 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A、B模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略设置 A-3 流量完整性保护 A-4 …

【整顿C盘】pycharm、chrome等软件,缓存移动

C盘爆了&#xff0c;特来找一下巨大的软件缓存&#xff0c;特此记录&#xff0c;跟随的各大教程&#xff0c;和自己的体会 一、爆炸家族JetBrains 这个适用于pycharm、idea、webstorm等等&#xff0c;只要是JetBrains家的&#xff0c;2020版本以上&#xff0c;都是一样的方法 p…