• Gorm框架学习---CRUD接口之创建



    本文内容摘抄自Gorm 2022-8月份官方文档教程,如果Gorm框架后续有更新,还是以最新版本的官方文档为准


    系列文章:

    Gorm框架学习–入门


    环境准备

    先确保能够连接上指定的数据库并且将相关表创建好,这里用mysql作为演示:

    package main
    
    import (
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    )
    
    const MYSQL_ADDR ="user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    
    type User struct {
        gorm.Model
    	Name     string
    	Age      int
    	Birthday time.Time
    }
    
    func main() {
    	DB = openDB()
    	createTable()
    }
    
    func createTable() {
    	DB.AutoMigrate(&User{})
    }
    
    func openDB() *gorm.DB {
    	db, err := gorm.Open(mysql.Open(MYSQL_ADDR), &gorm.Config{})
    	if err != nil {
    		panic("failed to connect database")
    	}
    	return db
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    创建

    创建记录

    func main() {
    	DB = openDB()
    	user := User{Name: "大忽悠", Age: 18, Birthday: time.Now()}
    
    	result := DB.Create(&user) // 通过数据的指针来创建
    
    	fmt.Println("返回插入数据的主键: ", user.ID)
    	fmt.Println("返回 error: ", result.Error)
    	fmt.Println("返回插入记录的条数: ", result.RowsAffected)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述


    用指定的字段创建记录

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

    	DB.Select("Name", "Age", "CreatedAt").Create(&user)
    	// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("大忽悠", 18, "2022-08-04 11:05:21.775")
    
    • 1
    • 2

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

    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")
    
    • 1
    • 2

    批量插入

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

    	var users = []User{{Name: "dhy1"}, {Name: "dhy2"}, {Name: "dhy3"}}
    	DB.Create(&users)
    
    	for _, user := range users {
    		fmt.Printf("id=%d,name=%s,age=%d\n", user.ID, user.Name, user.Age)
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    ERROR是因为Mysql5.7版本及以上版本的datetime值不能为’0000-00-00 00:00:00’,
    解决方法: 修改mysql.int
    在[mysqld]添加一项:sql_mode=NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,支持特殊的语法,这样就可以导入了,导入完毕后,移除兼容项即可。此方法简单,建议使用此方法。

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

    	var users = []User{{Name: "dhy1"}, {Name: "dhy2"}, {Name: "dhy3"}}
    	// 数量为 100
    	DB.CreateInBatches(users, 100)
    
    • 1
    • 2
    • 3

    UpsertCreate With Associations 也支持批量插入

    注意 使用CreateBatchSize 选项初始化 GORM 时,所有的创建& 关联 INSERT 都将遵循该选项

    func openDB() *gorm.DB {
    	//针对全局设置
    	db, err := gorm.Open(mysql.Open(MYSQL_ADDR), &gorm.Config{
    		CreateBatchSize: 1,
    	})
    	if err != nil {
    		panic("failed to connect database")
    	}
    	return db
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    	var users = []User{{Name: "dhy1"}, {Name: "dhy2"}, {Name: "dhy3"}}
    	//针对某次会话设置
    	DB.Session(&gorm.Session{CreateBatchSize: 1}).Create(users)
    
    • 1
    • 2
    • 3

    创建钩子

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

    钩子方法常与模板方法模式搭配使用,通常暴露给用户自定义相关组件,以此提高框架整体可扩展性

    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
      u.UUID = uuid.New()
    
        if u.Role == "admin" {
            return errors.New("invalid role")
        }
        return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    //都是针对单词会话进行设置的
    DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
    
    DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
    
    DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    根据 Map 创建

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

    	DB.Model(&User{}).Create(map[string]interface{}{
    		"Name": "DHY", "Age": 18,
    	})
    
    	// batch insert from `[]map[string]interface{}{}`
    	DB.Model(&User{}).Create([]map[string]interface{}{
    		{"Name": "dhy1", "Age": 18},
    		{"Name": "dhy2", "Age": 20},
    	})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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


    使用 SQL 表达式、Context Valuer 创建记录

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

    	// 通过 map 创建记录
    	DB.Model(User{}).Create(map[string]interface{}{
    		"Name": clause.Expr{SQL: "UPPER(?)", Vars: []interface{}{"dhy"}},
    		"age":  18,
    	})
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对应创建的sql语句为:

    INSERT INTO `users` (`name`,`age`) VALUES (UPPER('dhy'),18)
    
    • 1

    通过自定义类型创建记录:

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

    高级选项

    关联创建

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

    upsert: 存在时更新,不存在时插入

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

    您也可以通过 Select、 Omit 跳过关联保存,例如:

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

    默认值

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

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

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

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

    type User struct {
    	gorm.Model
    	Name     string    `gorm:"default:18"`
    	Age      int       `gorm:"default:100"`
    	Birthday time.Time `gorm:"default:2022-08-23 21:28:27.329"`
    }
    
    func main() {
    	DB = openDB()
    	//我想保存相关数据类型的零值到数据库,但是由于默认值的存在,插入数据库的还是默认值
    	DB.Create(&User{
    		Name: "",
    		Age:  0,
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    指针解决法:

    type User struct {
    	gorm.Model
    	Name     *string   `gorm:"default:18"`
    	Age      *int      `gorm:"default:100"`
    	Birthday time.Time `gorm:"default:2022-08-23 21:28:27.329"`
    }
    
    func main() {
    	DB = openDB()
    	str := ""
    	age := 0
    	DB.Create(&User{
    		Name: &str,
    		Age:  &age,
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Scanner/Valuer接口解决法:

    type myString string
    
    type User struct {
    	gorm.Model
    	Name     myString
    	Age      int       `gorm:"default:100"`
    	Birthday time.Time `gorm:"default:2022-08-23 21:28:27.329"`
    }
    
    //Scan 在从数据库读取记录到结构体时,当需要往某个字段注入值时,会先检查该字段是否实现了对应的Scan接口
    //如果实现了,就利用该接口完成赋值
    func (s *myString) Scan(src any) error {
    	if src == nil {
    		*s = ""
    		return nil
    	}
    	//... 我们需要在下面完成对当前字段的赋值
    	return nil
    }
    
    func (s myString) Value() (driver.Value, error) {
    	if s == "" {
    		//如果为空,就记录一下
    		fmt.Println("当前字段为空")
    	}
    	//注意: 如果返回的类型也实现了Value接口,会继续调用
    	//如果这里直接将s返回,会造成死循环,因为会不断去调用s的Value接口
    	return "hhhhh", nil
    }
    
    func main() {
    	DB = openDB()
    	//在获取每个字段值时,会检查对应的字段有没有实现Value接口
    	//如果实现了,就获取Value接口的返回值,作为最终结果
    	DB.Create(&User{
    		Name: "xpy",
    		Age:  0,
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    Gorm操作对象属性前,会先去寻找Scan和Value方法,如果有则调用,这一点类似Java中操作对象属性通常使用Getter和Setter方法一般。


    default:(-)标签可以让我们在字段为零值时,忽略该字段的插入。

    type User struct {
    	gorm.Model
    	Name     myString
    	Age      int       `gorm:"default:(-)"`
    	Birthday time.Time `gorm:"default:2022-08-23 21:28:27.329"`
    }
    
    func main() {
    	DB = openDB()
    	DB.Create(&User{
    		Name: "xpy",
    		Age:  0,
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • Age为0时,生成的insert语句忽略了该字段的插入

    在这里插入图片描述

    • Age不为0时,insert语句才会添加对应字段的插入

    在这里插入图片描述


  • 相关阅读:
    现代卷积神经网络 合并行连结的网络(GoogLeNet)
    P1271 【深基9.例1】选举学生会 题解
    预测和分类钻孔的毛刺钻孔切削ANN预测
    数据库及ADO.NET学习(三)
    spring 循环依赖以及解决方式
    RTOS(ENV)串口DMA接收GPS数据并解析
    kaggle电子邮件分类xgboost建模可视化模型评估混淆矩阵范例
    [Linux] 基于阻塞队列的生产者消费者模型
    Go 语言学习总结(9)—— Go 与 Java 全面对比总结
    JVM<二>JVM调优(你想要的调优都在这里了)
  • 原文地址:https://blog.csdn.net/m0_53157173/article/details/126494601