gorm

本贴最后更新于 294 天前,其中的信息可能已经事过境迁

Gorm 框架概述1

使用 Gorm 框架2

模型常用标签3

增删改查4

创建5

查询6

更新7

删除8

原生 SQL 和 SQL 生成器9

关联10

预加载11

多态12

事务13

自定义数据类型14

Hook15


  1. Gorm 框架概述

    • 什么是 ORM

      ORM(对象关系映射)是一种编程技术,用于在关系型数据库和面向对象编程语言(例如 Go)之间建立映射关系。它允许开发人员使用面向对象的方式操作数据库表中数据,而无需直接编写 SQL 查询语句。

    • ORM 的主要目标

      将数据库中的表和记录 映射到编程语言中的类和对象,以便开发人员可以通过对象和类来进行数据库操作,例如插入、更新、删除和查询数据,而不需要关注底层数据库的细节。

    • 什么是 Gorm 框架

      Gorm 框架是 ORM 技术 的 Go 实现

    • Gorm 框架作用

      Gorm 框架它封装了底层数据库引擎的细节,并提供了一系列方法和功能,以简化开发人员对数据库的访问和操作。

  2. 使用 Gorm 框架

    • 引入驱动

      连接不同的数据库都需要导入对应数据的驱动程序

      import "gorm.io/driver/mysql"
      import "gorm.io/driver/postgres"
      import "gorm.io/driver/sqlite"
      import "gorm.io/driver/mssql"
      
    • 定义一个模型(struct​)字段与数据库的表结构一致

      在使用 Gorm 时,通常我们需要在代码中定义模型(Models 结构体)与数据库中的数据表进行映射,在 GORM 中模型(Models)通常是正常定义的结构体。

      为了方便模型定义,Gorm 内置了一个 gorm.Model​​结构体。gorm.Model​​是一个包含了 ID​​, CreatedAt​​, UpdatedAt​​, DeletedAt​​四个字段的 Golang 结构体。

      // gorm.Model 定义
      type Model struct {
      	ID        uint `gorm:"primary_key"`
      	CreatedAt time.Time
      	UpdatedAt time.Time
      	DeletedAt *time.Time
      }
      
      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
      }
      

    • 连接数据库

      • 普通连接

        import (
         "gorm.io/driver/mysql"
          "gorm.io/gorm"
        )
        
        func main() {
        
          dsn:="root:123456@(127.0.0.1:3306)/db1?charset=utf8mb4&parseTime=True&loc=Local"
        	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
        }
        
        {username}:{password}@tcp({host}:{port})/{dbname}?charset=utf8&parseTime=True&loc=Local
        
      • 数据库连接池

        • 什么是数据库连接池

          数据库连接 是应用程序与数据库之间的通信通道,用于执行数据库操作。

          数据库连接池 存放预先创建的数据库连接,用于提供可重复使用的数据库连接,而不是每次都建立新的数据库连接。

        • 如何使用 Gorm 数据库连接池

          // 全局数据库 db
          var db *gorm.DB
          
          // 包初始化函数,可以用来初始化 gorm
          func init() {
          	// 配置 dsn
          
          	// err
          	var err error
          	// 连接 mysql 获取 db 实例
          	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
          	if err != nil {
          		panic("连接数据库失败, error=" + err.Error())
          	}
          
          	// 设置数据库连接池参数
          	sqlDB, _ := db.DB()
          	// 设置数据库连接池最大连接数
          	sqlDB.SetMaxOpenConns(100)
          	// 连接池最大允许的空闲连接数,如果没有sql任务需要执行的连接数大于20,超过的连接会被连接池关闭
          	sqlDB.SetMaxIdleConns(20)
          }
          
          // 获取 gorm db,其他包调用此方法即可拿到 db
          // 无需担心不同协程并发时使用这个 db 对象会公用一个连接,因为 db 在调用其方法时候会从数据库连接池获取新的连接
          func GetDB() *gorm.DB {
          	return db
          }
          
          func main() {
          	// 获取 db
          	db := tools.GetDB()
          
          	// 执行数据库查询操作
          }
          
    • 对数据库表管理


      在 Gorm 中建立数据库表

      1. gorm.DB.AutoMigrat()​ 自动迁移方法

        自动迁移方法,如果模型对应表存在则不创建,不存在按照模型创建新表。

        但是不支持字段修改删除,为避免数据意外丢失

        // 自动迁移多个数据表
        gorm.DB.AutoMigrate(&User{}, &Product{}, &Order{}) 
        
        // 可以通过 gorm.DB.Set() 设置表附加参数,下面设置表的存储引擎为InnoDB
        gorm.DB.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
        
      2. gorm.DB.Migrator().CreateTable()​创建表

      3. 检查表是否存在

        // 检测 User结构体 对应的表是否存在
        gorm.DB.Migrator().HasTable(&User{})
        
        // 检测 表名users的表 是否存在
        gorm.DB.Migrator().HasTable("users")
        
      4. 删除表

        // 删除 User 结构体对应的表
        gorm.DB.Migrator().DropTable(&User{})
        // 根据表名删除表
        gorm.DB.Migrator().DropTable("users")
        
      5. 表字段的修改

        // 修改字段
        gorm.DB.Migrator().DropColumn(&User{}, "Name")
        
      6. 表的索引的管理

        // 为字段添加索引
        gorm.DB.Migrator().CreateIndex(&User{}, "Name")
        // 修改索引名
        gorm.DB.Migrator().RenameIndex(&User{}, "Name", "Name2")
        // 为字段删除索引
        gorm.DB.Migrator().DropIndex(&User{}, "Name")
        // 检查索引存在
        gorm.DB.Migrator().HasIndex(&User{}, "Name")
        

    事务、错误类型、Debug、原生语句

    • 事务

      在事务是一组具备 ACID 特性的数据库操作(如插入、更新、删除等)。

      这些操作要么全部成功执行,要么全部回滚(即撤销)。

      事务提供了一种机制来保证数据库操作的原子性、一致性、隔离性和持久性(ACID),通常用于处理需要同时执行多个操作并保持数据的完整性和一致性的场景。

      • 自动事务 Transction()​​

        db.Transction(func(tx *gorm.DB) error {
        	// 在事务中插入数据
        	if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
        		// 返回任何错误都会回滚事务
        		return err
        	}
        	// 在事务中插入数据
        	if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
        		// 返回任何错误都会回滚事务
        		return err
        	}
        	// 返回 nil 提交事务
        	return nil
        })
        
      • 手动事务

        // 开启事务
        tx := db.Begin()
        
        // 事务之更新操作(库存减一),拿到影响的行数
        rowsAffected := tx.Model(&food).Where("stock > 0").Update("stock", gorm.Expr("stock - 1")).RowsAffected
        if rowsAffected == 0 {
        	// 说明没有库存需要回滚
        	tx.Rollback()
        	return
        }
        
        // 事务之插入操作,创建订单
        err := tx.Create(&u).Error
        if err != nil {
        	tx.Rollback()
        } else {
        	tx.Commit()
        }
        
      • 回滚点

        GORM 提供了 SavePoint​、Rollbackto​ 方法,来提供保存点以及回滚至保存点功能,例如:

        tx := db.Begin()
        tx.Create(&user1)
        
        tx.SavePoint("sp1")
        tx.Create(&user2)
        tx.RollbackTo("sp1") // Rollback user2
        
        tx.Commit() // Commit user1
        
    • 错误类型和 Debug

      gorm.DB​类有个 Error​字段,用于表示数据库操作是否错误,Error​ 默认 nil​。

      Where​ 会返回 *gorm.DB​类型值,如果查失败 Error​字段,就不 nil​,之后使用 Error.Is()​来判断是什么类型错误。

      // 举例
      result := db.Where("username = ?", "abcnull").First(&u)
      if errors.Is(result.Error, gorm.ErrRecordNotFound) {
      	fmt.Println("找不到记录")
      	return
      }
      
      // ErrRecordNotFound:查找不到记录时候的错误
      

      gorm.DB.Debug()​​方法可以打印一条 sql​​语句

      result := db.Debug().Where("username = ?", "tizi365").First(&u)
      
    • 补充内容

      直接执行 原生 sql 语句

      // sql 语句,其中有多处 ?
      sql := ""
      
      // 原生执行 sql 语句绑定多个参数
      db.Raw(sql, "")
      

  3. 模型常用标签

    • 读写权限标签

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

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

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

      嵌入模型有默认嵌入、和标签嵌入

      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
      }
      

      你可以通过标签个嵌入结构体的标签加上前缀

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

      标签名 说明
      colum 列名
      type 列的数据类型
      precision 列的精度
      size 列数据类型的长度
      scale 列的大小
      not null 指定列的 NOT NULL
      autoIncrement 指定列自动增长
      autoIncrementIncrement 指定列自动增长步长
      primaryKey 主键
      unique 唯一键
      default default
      index 根据参数创建索引
      uniqueIndex index​​ 相同,但创建的是唯一索引
      check 创建检查约束,例如 check:age > 13​​,查看 约束 获取详情
      serializer 指定数据序列化到数据库中使用的序列化方法

  4. 增删改查

    CRUD	最常用的方法	其他方法
    查询	Find	First/Last/Take/FindInBatches/FirstOrInit/FirstOrCreate/Count/Scan
    创建	Create	CreateInBatches/Save
    更新	Updates	Update/UpdateColumn/UpdateColumns
    删除	Delete	
    

    如果模型结构体类型的字段上使用了 GORM 提供的标签。

    那么在执行插入操作时会调用了 GORM 的自动填充机制,该机制会检查模型结构体类型的字段上标签,并根据标签规则来设置相应的值。

    结构体 单个插入

    db.Create()

    // 连接数据库后获取一个 db
    
    // 插入一条数据
    // INSERT INTO `users` (`username`,`password`,`createtime`) VALUES ('abcnull','123456','1540824823')
    if err := db.Create(&u).Error; err != nil {
    	fmt.Println("插入失败", err)
    	return
    }
    

    切片 map 批量插入

    • gorm.DB.Where

      Where​ 方法:用于添加查询条件。您可以使用 Where 方法链式调用来添加多个查询条件

      添加查询条件可以是字符串、结构体、map

      // 连接数据库后获取一个 db
      
      // 定义一个用于保存查询的元组的结构体
      u := User{}
      
      // 查询第一条数据
      // SELECT * FROM `users` WHERE (username = 'abcnull') LIMIT 1
      result := db.Where("username = ?", "abcnull").First(&u)
      if errors.Is(result.Error, gorm.ErrRecordNotFound) {
      	fmt.Println("找不到记录")
      	return
      }
      
      db.Where("age > ?", 18).Where("gender = ?", "male").Find(&users)
      // 将查询年龄大于 18 且性别为男性的用户记录,并将结果存储在 users 变量中。
      

      也可以配合 **Or()** ​ ** **Not()**方法组成查询条件**

    • Take​ 方法:用于查询符合条件的任意一条元组。

      // Take 查询一条记录
      // SELECT * FROM `user` LIMIT 1
      db.Take(&u)
      
    • First​ 方法:用于查询符合条件的第一条元组。

      同时也可以,在**First()**添加查询条件。(内联条件)

      // First 根据主键 id 排序后的第一条,查询不到会报出 gorm.ErrRecordNotFound 异常
      // SELECT * FROM `user` ORDER BY `id` LIMIT 1
      db.First(&u)
      
    • Last​ 方法:用于查询符合条件的最后一条元组。

      // Last 根据主键 id 排序后最后一条,查询不到会报出 gorm.ErrRecordNotFound 异常
      // SELECT * FROM `user` ORDER BY `id` DESC LIMIT 1
      db.Last(&u)
      
    • Find​ 方法:用于查询符合条件的所有元组

      // Find 查询多条记录,返回数组
      // SELECT * FROM `user`
      db.Find(&u)
      
    • Select​方法:选择指定列显式

      // SELECT id,title FROM `user` WHERE `id` = '1' AND ((id = '1')) LIMIT 1
      db.Select("id,title").Where("id = ?", 1).Take(&u)
      
    • gorm.DB.Order

      Order​ 方法:用于指定查询结果的排序方式。例如,db.Order("age DESC").Find(&users)​ 将按照年龄的降序对用户记录进行查询,并将结果存储在 users​ 变量中。

      // Order 表示排序方式,其中写 sql 部分
      // SELECT * FROM `user` WHERE (create_time >= '2018-11-06 00:00:00') ORDER BY create_time desc
      db.Where("create_time >= ?", "2018-11-06 00:00:00").Order("create_time desc").Find(&u)
      
      // Limit Offset 分页常用
      // SELECT * FROM `user` ORDER BY create_time desc LIMIT 10 OFFSET 0
      db.Order(create_time desc").Limit(10).Offset(0).Find(&u)
      

    • Update()​方法

      // 连接数据库后获取一个 db
      
      // 定义一个保存数据的结构体
      u := User{}
      
      // 更新用户表的密码
      // UPDATE `users` SET `password` = '654321' WHERE (username = 'abcnull')
      db.Model(&u).Where("username = ?", "abcnull").Update("password", "654321")
      
    • 与修改相关的常见函数

      // Save 更新函数
      db.Save(&u)
      
      // Update 更新某条记录的单个字段
      db.Model(&u).Update("price", 25)
      
      // Update 跟新所有记录的单个字段
      db.Model(&User{}).Update("price", 25)
      
      // Update 自定义条件而非主键更新某字段
      db.Model(&User{}).Where("create_time > ?", "2018-11-06 20:00:00").Update("price", 25)
      

      更新表达式 Update("stock", gorm.Expr("stock + 1"))

    • Delete()​​方法

      // 连接数据库后获取一个 db
      
      // 定义一个保存数据的结构体
      u := User{}
      
      // 删除某条记录
      // DELETE FROM `users`  WHERE (username = 'abcnull')
      db.Where("username = ?", "abcnull").Delete(&u)
      
  5. 创建

    • 单独创建

      结构体

      根据 map 创建记录时,association 不会被调用,且主键也不会自动填充

    • 批量创建

      模型结构体切片 ** 批量插入**

      map[string]interface{}批量插入

      []map[string]interface{}批量插入

      var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
      db.Create(&users)
      
      for _, user := range users {
      	user.ID // 1,2,3
      }
      
      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},
      })
      
      
    • 跳过字段 黑名单

      添加记录时候除了某些字段其他字段都给值,黑名单

    • 指定字段 白名单

      添加记录时候只给指定字段值,对应 SQL 中的 SELECT​。

  6. 查询

    • 如何接收查询结果

      结构体 查一个

      模型结构体切片 查多个

      map[string]interface{}查多个

      []map[string]interface{}查多个

    查询

    • 主键查询

    • where()

      gorm.DB.Where

      Where​ 方法:用于添加查询条件。您可以使用 Where 方法链式调用来添加多个查询条件

      添加查询条件可以是字符串、结构体、map

      // 连接数据库后获取一个 db
      
      // 定义一个用于保存查询的元组的结构体
      u := User{}
      
      // 查询第一条数据
      // SELECT * FROM `users` WHERE (username = 'abcnull') LIMIT 1
      result := db.Where("username = ?", "abcnull").First(&u)
      if errors.Is(result.Error, gorm.ErrRecordNotFound) {
      	fmt.Println("找不到记录")
      	return
      }
      
      db.Where("age > ?", 18).Where("gender = ?", "male").Find(&users)
      // 将查询年龄大于 18 且性别为男性的用户记录,并将结果存储在 users 变量中。
      

      也可以配合 **Or()** ​​ ** **Not()** ​​法组成查询条件**

    • 内联(省略 where,直接在得到结果的 Find​​ First​​ Last​​等后面书写条件-字符串 结构体 map)

    • 智能选择字段

    • 与查询相关的方法有 Where/first/last/take/find/Order​这些方法如果传入 结构体实例指针 搜出来 0 条,会报 errRecordNotFound​,传入结构体实例指针切片 搜出来 0 条 则不会报错。


      gorm.DB.Where

      Where​​ 方法:用于添加查询条件。您可以使用 Where 方法链式调用来添加多个查询条件

      添加查询条件可以是字符串、结构体、map

      // 连接数据库后获取一个 db
      
      // 定义一个用于保存查询的元组的结构体
      u := User{}
      
      // 查询第一条数据
      // SELECT * FROM `users` WHERE (username = 'abcnull') LIMIT 1
      result := db.Where("username = ?", "abcnull").First(&u)
      if errors.Is(result.Error, gorm.ErrRecordNotFound) {
      	fmt.Println("找不到记录")
      	return
      }
      
      db.Where("age > ?", 18).Where("gender = ?", "male").Find(&users)
      // 将查询年龄大于 18 且性别为男性的用户记录,并将结果存储在 users 变量中。
      

      也可以配合 **Or()** ​ ** **Not()**方法组成查询条件**


      gorm.DB.Model()

      用于指定要操作的模型,后续的数据库操作都会在模型对应的数据表上进行。


      gorm.DB.Table()

      gorm.DB.Model()​类似,但 Table()​是指定要操作的数据库表的名称。它可以绕过模型的映射,直接在指定的表上执行数据库操作。


      gorm.DB.Take

      Take​ 方法:用于查询符合条件的任意一条元组。

      // Take 查询一条记录
      // SELECT * FROM `user` LIMIT 1
      db.Take(&u)
      

      gorm.DB.First

      First​ 方法:用于查询符合条件的第一条元组。

      // First 根据主键 id 排序后的第一条,查询不到会报出 gorm.ErrRecordNotFound 异常
      // SELECT * FROM `user` ORDER BY `id` LIMIT 1
      db.First(&u)
      

      同时也可以,在**First**添加查询条件。(内联条件)


      gorm.DB.Last

      Last​ 方法:用于查询符合条件的最后一条元组。

      // Last 根据主键 id 排序后最后一条,查询不到会报出 gorm.ErrRecordNotFound 异常
      // SELECT * FROM `user` ORDER BY `id` DESC LIMIT 1
      db.Last(&u)
      

      gorm.DB.Find

      Find​ 方法:用于查询符合条件的所有元组

      // Find 查询多条记录,返回数组
      // SELECT * FROM `user`
      db.Find(&u)
      

      gorm.DB.Select

      // SELECT id,title FROM `user` WHERE `id` = '1' AND ((id = '1')) LIMIT 1
      db.Select("id,title").Where("id = ?", 1).Take(&u)
      

      gorm.DB.Order

      Order​ 方法:用于指定查询结果的排序方式。例如,db.Order("age DESC").Find(&users)​ 将按照年龄的降序对用户记录进行查询,并将结果存储在 users​ 变量中。

      // Order 表示排序方式,其中写 sql 部分
      // SELECT * FROM `user` WHERE (create_time >= '2018-11-06 00:00:00') ORDER BY create_time desc
      db.Where("create_time >= ?", "2018-11-06 00:00:00").Order("create_time desc").Find(&u)
      
      // Limit Offset 分页常用
      // SELECT * FROM `user` ORDER BY create_time desc LIMIT 10 OFFSET 0
      db.Order(create_time desc").Limit(10).Offset(0).Find(&u)
      
  7. 更新

    image

    更新以查询为基础,查询再更新。

    • 更新一个或者更新多个

      结构体 更一个

      模型结构体切片 更多个

      map[string]interface{}​​ 更多个

      []map[string]interface{}更多个

    • Updates()

    • Save()

      根据给定的 **结构体、map、切片 ** 进行更新

      image

  8. 删除

    删除以更新为删除,先查再删除。

    • 删除一个或者多个

      给结构体 **删一个 **

      模型结构体切片 ** 删多个**

      map[string]interface{}​​ **删多个 **​[]map[string]interface{}​​ 删多个

    • 硬删除

      默认软删除,不删除时间标签生成的字段。

      image添加 Unscoped()​硬删除

  9. 原生 SQL 和 SQL 生成器

    • 如何使用原生 SQL 语句

      1. 方法一

        Raw()​方法和 Scan()​方法

        db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)
        
      2. 方法二

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

  10. 关联

    belongs to

    belongs to 建立

    • 约束外键 + 默认引用

      image

    • 改写外键 + 默认引用

      image

    • 改写外键 + 指定外键引用

      /*
      属于
      Information 属于 Student : Information是查询主体
      */
      type Student struct {
      	ID   int64
      	Sfz  string `gorm:"primaryKey"`
      	Name string
      	Sex  uint8
      	Age  uint8
      }
      type Information struct {
      	ID                 int64 `gorm:"primaryKey"`
      	Addres             string
      	TestforeignKeyname int64
      	/*
      		外键列是TestforeignKeyname,指定引用 Student表Sfz列的值。
      	*/
      	Student Student `gorm:"foreignKey:TestforeignKeyname;references:Sfz"`
      }
      

      image

      外键的引用字段 有重要四个注意点:

      1. 引用字段得是 主键 或者 索引。
      2. 如果出现 Error 1170: BLOB/TEXT ​​错误,看这篇文章。gorm 引用关联标签_-logieeU 的博客
      3. 如果引用字段是主键且是 INT​​类型,插入记录可以自增。对于其他类型的主键列,如字符型(VARCHAR​​)、日期型(DATE​​)等,MySQL 不会自动生成值。
      4. 注意标签的大小写,和规范。

    belongs to CRUD

    has one

    has one 建立

    // Users 有一张 CreditCard
    type Users struct {
    	Sfz        int `gorm:"type:int(100);primaryKey"`
    	Name       string
    	Age        uint
    	Sex        string
    	CreditCard CreditCard
    }
    
    type CreditCard struct {
    	ID                 uint `gorm:"primaryKey"`
    	Bank               string
    	Deposit            uint
    	TestforeignKeyname int
    }
    
    
    • 约束外键 + 默认引用

      image

    • 改写外键 + 默认引用

      image

    • 改写外键 + 指定外键引用

      image

    has one CRUD

    has many

    has many 建立

    • 约束外键 + 默认引用

      image

    • 改写外键 + 默认引用

      image

    • 改写外键 + 指定外键引用

      image

    has many CRUD

    关联模式下 has many 的 CRUD

    • 删除

      第一个问题 软删除和硬删除

      如果字段带有 gorm:"not null"​​标签,则不能使用默认删除(也就是软删除),需要带上 Unscoped()​​方法永久删除。

      软删除出错

      2023/06/30 16:31:13 C:/Users/win/Desktop/panel-orm-example/main.go:162 Error 1048 (23000): Column 'serverid' cannot be null
      [141.669ms] [rows:0] UPDATE `inbounds` SET `serverid`=NULL WHERE `inbounds`.`serverid` = 1 AND `inbounds`.`inboundid` = 2
      2023/06/30 16:31:13 Error 1048 (23000): Column 'serverid' cannot be null
      

      软删除生成的 SQL 语句中有 SET serverid =NULL​​,但是 serverid​​字段被标签设置成不为空了,所以冲突。

      硬删除正常(硬删除生成的 SQL 没有​SET serverid =NULL​​ )

      image

      带上主键负责指定删除那个 Server 记录关联的多个 Inbound 记录的某一个。

      第二个问题 如何进行关联删除,删除主记录,关联的记录都删除。

      通过在结构体字段的 gorm​标签中使用 constraint​标签,可以指定关联模式删除的行为。

      constraint:OnDelete:CASCADE​,指定了关联模式删除的行为。这意味着当删除一个主记录时,相关联的记录也会被级联删除。

      除了 CASCADE​,还有其他可用的关联模式删除选项,如 SET NULL​、SET DEFAULT​等,可以根据具体需求进行选择。

    • 替换

      替换的坑和软删除根本原因一样,因为奇淼说 替换(Replace​​)是分两步 一全删除 二重新添加,所以第一步删除也会有 SET serverid =NULL​​问题,解决办法和软删除一样,带上 Unscoped()​​方法永久删除。

      image

    many to many

    many to many 建立

    Many to Many 会在两个 model 中添加一张连接表。通过连接表 表示 Many to Man 关联关系。

    注意:

    Many to Many 标签例如:​gorm:"many2many:test_tb;foreignKey:GirlSfz;References:DogSfz;"

    连接表名 ​test_tb

    之前的 belongs to has one has many 三种关联关系都是,一个 foreignKey​和 References​确定外键名和外键的引用值。

    但 Many to Many 中一个**foreignKey**就确定外键名和外键的引用值,一个**References**就确定外键名和外键的引用值。

    • 双向添加 指定连接表名 + 默认外键 + 默认引用

      image

      image

    • 双向添加 指定连接表名 + 指定外键 + 指定引用

      image

      image

    预加载查询

    • 普通预加载查询

      image

    many to many 关联模式

    基于 双向添加 指定连接表名 + 默认外键 + 默认引用 之上

    • 关联查询

      ​​​image

      image

      image

    • 关联添加

      image

    • 关联修改

      修改=删除 + 重新添加

      image

    • 关联删除

      image

    • 关联清除

      清除所有关联关系

      image

  11. 预加载

    Gorm 关联模式与预加载 - Nativus' Space (naiv.fun)

    • Gorm 有两种预加载:Joins​ 和 Preload

    Joins​类似 SQL 中 JOIN​命令。

    Preload​​则是 Gorm 引入的概念,主要作用在处理某个 Model 时将关联的 Model 中的信息预先读取出来。

    Preload​只预加载某个 Model 时将关联的 Model ,这层的信息预先读取出来。

    • Preload​的分类

      • 普通预加载

        先将两个 Model 关联,User 和 Score 是 Belong_to​ 的关系。每个 Score 都属于一个 User,换句话说就是每个 User 都有一个 Score。

        type User struct {
        	*gorm.Model
        
        	Username string `json:"username"`
        	Password string `json:"password"`
        	Type     string `json:"type"`
        }
        
        type Score struct {
        	*gorm.Model
        
        	UserId uint `json:"user_id"` // Foreign Key
        	User   User `gorm:"foreignKey:UserId"`
        
        	Total  int `json:"total"`
        	Web    int `json:"web"`
        	Bin    int `json:"bin"`
        	Pwn    int `json:"pwn"`
        	Crypto int `json:"crypto"`
        	Misc   int `json:"misc"`
        }
        

        正常查询 Score 模型对应表的元组时:

        var score Score
        DB.Model(Score{}).Take(&Score)
        

        这样查询出来的元组填充到 score 变量。但里如果试图通过 score.User.Username​ 获取相关联的 User 的 Username​,得到的只会是 ""​原因是数据库里面 Score 表没有保存 User 表的信息,只有一个用于关联的外键 user_id​,所以通过一次查询 Score 不可能把 User 的信息取出来。

        我们需要使用 Preload​处理

        var SocreList []Score
        
        DB.Preload("User").Find(&ScoreList)
        

        在两个 SELECT​ 执行完毕后,第二条(预加载)指令运行后的结果还会被写入到 ScoreList​ 中。此时我们调用 ScoreList[0].User.Username​ 就可以获取到第 1 条记录对应的 User 的信息。

      • 内联带条件预加载:在处理某个 Model 时将关联的 Model 中的信息按照条件筛选预先读取出来。

      • 函数自定义条件预加载:

      • 链式预加载

  12. 多态

    多态只在 has one 、has many。多态的结构体数据不可用同时被多人拥有。

    • 为什么要有多态

      例如:有御姐模型 、夹子模型、风车模型

      我想御姐 has one/ has many 风车,同时夹子 has one/ has many 风车。

      可以通过之前的 has one/ has many 关联建立方法。也就是**风车分别加上两个外键,每个外键分别引用御姐 和 夹子的主键值。 **

      但是如果场景扩大 有多个模型需要 has one/ has many 风车,风车模型的外键将大量增长。

      如何解决这个问题呢?使用多态。可以不需要再风车模型增加大量外键去保存关联关系,主要是通过字段范围(实现) ,还是上面的案例,现在只需要在风车模型加上 ** ** 多态类型 和 多态 ID 字段。就可以了。多态类型字段的值范围就是 御姐表的表名和夹子表的表名。

    • 多态标签

      gorm:"polymorphic:Owner;polymorphicValue:master"

      polymorphic​设置多态类型 和 多态 ID 字段的名字。

      polymorphicValue​ 设置多态类型的字段值(通常字段范围应该是 拥有者的表名,来关联拥有者,不过我们可以通过 polymorphicValue​自定义。)

    • 多态 ID 字段的作用

      夹子表中 可能有夹子 1 号 夹子 2 号两条记录,他们都想有风车,那么在风车表上 夹子 1 号的风车记录多态 ID 字段的值就是夹子 1 号主键值。以此类推,用来辨别是那个夹子。

  13. 事务

    • 什么是事务

      一组数据库操作的执行单元,要么成要么不作。

    • 怎么组事务

      通过 Transaction()​方法可以组事务

      • 普通事务

        db.Transaction(func(tx *gorm.DB) error {
        	// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
        	if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
        		// 返回任何错误都会回滚事务
        		return err
        	}
        
        	if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
        		return err
        	}
        
        	// 返回 nil 提交事务
        	return nil
        })
        
      • 嵌套事务

        里层 err 回滚 外层 nil 可以不回滚

        image

      • 自定义事务

        image

        事务要在提交前组成

        image

        回滚点回滚

  14. 自定义数据类型

    • 为什么要自定义数据类型

      很多数据库、编程语言的数据类型系统都不同,但是他们都可以存储字节流,是通用的。

      比如 JSON 数据反序列化映射到 Go 结构体,Go 结构体序列化 字节流(字节流),直接存入数据库,到时拿来就用。

      1. go 的大部分数据类型 应该都可以通过 gorm 映射到对应的数据库数据类型
      2. 自定义数据类型不是模型,只能作为模型中的字段的数据类型
        image
      3. 通常我们使用 database/sql 已经定义好的自定义类型。
    • 如何自定义数据类型

      为数据类型添加两个方法

      • Value()​方法 driver.value​类型返回值,为你要存入数据库的数据。

      • Scan()​方法,value interface{}​为从数据库拿到的数据一般是 []byte​。接受者 c 为最终渲染到的实例上。

      ​​image​​

    • 如何规定存入的自定义数据在数据库的数据类型。

      使用标签

      image

  15. Hook

    GORM 允许用户定义的钩子有 BeforeSave​, BeforeCreate​, AfterSave​, AfterCreate​。

    创建记录时将调用这些钩子方法。

    • 如何给使用 Hook

      给模型结构体添加 BeforeSave​​, BeforeCreate​​, AfterSave​​, AfterCreate​​方法

      例如给 User​​模型结构体类型 添加​BeforeSave​​

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

      使用 SkipHooks​ 会话模式

1 操作
chabing 在 2023-11-04 09:53:58 更新了该帖

相关帖子

回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 反馈

    Communication channel for makers and users.

    123 引用 • 908 回帖 • 221 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3169 引用 • 8208 回帖
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 605 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 25 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 417 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 620 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    334 引用 • 622 回帖
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 5 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    285 引用 • 248 回帖 • 105 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    76 引用 • 390 回帖 • 1 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 524 关注
  • 导航

    各种网址链接、内容导航。

    37 引用 • 168 回帖
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    324 引用 • 1395 回帖
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    82 引用 • 37 回帖
  • 博客

    记录并分享人生的经历。

    272 引用 • 2386 回帖
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 669 关注
  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    405 引用 • 3557 回帖
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    175 引用 • 407 回帖 • 497 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    148 引用 • 3769 回帖
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    123 引用 • 168 回帖
  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 212 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 468 关注
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    230 引用 • 1454 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖 • 12 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    103 引用 • 294 回帖
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1429 引用 • 10050 回帖 • 486 关注