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
}

创建

创建记录

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

在这里插入图片描述


用指定的字段创建记录

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

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

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

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

批量插入

要有效地插入大量记录,请将一个 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)
	}

在这里插入图片描述

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)

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
}
	var users = []User{{Name: "dhy1"}, {Name: "dhy2"}, {Name: "dhy3"}}
	//针对某次会话设置
	DB.Session(&gorm.Session{CreateBatchSize: 1}).Create(users)

创建钩子

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
}

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

	// batch insert from `[]map[string]interface{}{}`
	DB.Model(&User{}).Create([]map[string]interface{}{
		{"Name": "dhy1", "Age": 18},
		{"Name": "dhy2", "Age": 20},
	})

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

对应创建的sql语句为:

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

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

// 通过自定义类型创建记录
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)"))

高级选项

关联创建

创建关联数据时,如果关联值是非零值,这些关联会被 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` ...

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

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

指针解决法:

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

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

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,
	})
}
  • Age为0时,生成的insert语句忽略了该字段的插入

在这里插入图片描述

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

在这里插入图片描述


版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340