Go语言之GORM框架(四)——预加载,关联标签与多态关联,自定义数据类型与事务(完结篇)

前言

本来是想着写多表关系的,不过写了一半发现重复的部分太多了,想了想与其做一些重复性工作,不如把一些当时觉得抽象的东西记录一下,就当用一篇杂记完成专栏的最后一篇文章吧。

预加载

简单示例

预加载主要用于在多表关系中加载关联表的信息,在讲解预加载的类型之前我们先来看一个预加载的示例:

  • 相关表结构
type User struct {
	gorm.Model
	Name      string
	Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
	gorm.Model
	Name  string
	Users []User `gorm:"many2many:user_languages;"`
}

我们尝试往里面插入数据:

// 创建语言对象
	languages := []Language{
		{Name: "Golang"},
		{Name: "Python"},
		{Name: "Java"},
	}

	// 创建用户对象
	users := []User{
		{Name: "Alice", Languages: []Language{languages[0], languages[1]}},   // Alice 会说 Golang 和 Python
		{Name: "Bob", Languages: []Language{languages[1], languages[2]}},     // Bob 会说 Python 和 Java
		{Name: "Charlie", Languages: []Language{languages[0], languages[2]}}, // Charlie 会说 Golang 和 Java
	}

	// 将语言和用户数据插入到数据库中
	for _, lang := range languages {
		db.Create(&lang)
	}

	for _, user := range users {
		db.Create(&user)
	}

然后我们尝试利用预加载来查询:

users := []User{}
	db.Preload("Languages").Find(&users)
	fmt.Println(users)

这样我们不仅能搜寻到user,还能把user相关联的Languages打印出来,像这样:
在这里插入图片描述

Joins预加载

Joins预加载会使用left join加载关联数据,与其说是预加载其实更像一个关联查询,常用与ONE TO ONEBelongs To的多表关系中:

  • 表结构:
type Student struct {
	ID   uint        `gorm:"size:8"`
	Name string      `gorm:"size:20"`
	Info StudentInfo `gorm:"foreignKey:StudentID"` // 明确指定关联关系
}

type StudentInfo struct {
	ID        uint `gorm:"size:8"`
	Age       int  `gorm:"size:4"`
	Sex       bool `gorm:"size:4"`
	Email     *string
	StudentID uint `gorm:"size:8"`
}
  • 示例:
	var student Student
	db.Joins("Info").Take(&student)
	db.Joins("Info", db.Where("Info.Age = ?", 18)) //带条件的Joins
	fmt.Println(student)

条件预加载

	var student Student
	db.Where("age > ?", 18).Preload("Info").First(&student)  //方式一
	db.Preload("Info", "age > ?", 18).Find(&student)   //方式二
	fmt.Println(student)

自定义预加载

	var student Student
	db.Preload("Info", func(db *gorm.DB) *gorm.DB{
		return db.Where("age > ?", 18)
	}).Find(&student)
	fmt.Println(student)

嵌套预加载

这里我们来看一下官方给的示例:

// Customize Preload conditions for `Orders`
// And GORM won't preload unmatched order's OrderItems then
db.Preload("Orders", "state = ?", "paid").Preload("Orders.OrderItems").Find(&users)

这段代码的意思是,在加载用户信息时,只预加载订单状态为 “paid” 的订单数据,并且同时预加载这些订单的订单项信息。这样做可以确保在查询用户数据时,只加载特定状态的订单及其订单项数据,而不会加载其他状态的订单信息。

#关联标签与多态关联

多态关联

关于多态关联我们先来看一个实例:

package main

import (
	"gorm.io/gorm"
	"gorm/ConnectDB"
)

type Boy struct {
	gorm.Model
	Name string
	Toys []Toy `gorm:"polymorphic:Owner"`
}

type Girl struct {
	gorm.Model
	Name string
	Toys []Toy `gorm:"polymorphic:Owner"`
}

type Toy struct {
	gorm.Model
	Name      string
	OwnerID   uint
	OwnerType string
}

func main() {
	db, err := ConnectDB.Connect()
	if err != nil {
		panic(err)
	}

	err = db.AutoMigrate(&Boy{}, &Girl{}, &Toy{})
	if err != nil {
		panic(err)
	}

	db.Create(&Boy{
		Name: "张三",
		Toys: []Toy{
			{Name: "玩具1"},
			{Name: "玩具2"},
		},
	})

	db.Create(&Girl{
		Name: "三玖",
		Toys: []Toy{
			{Name: "玩具3"},
			{Name: "玩具4"},
		},
	})
}

它创建出来的表:
在这里插入图片描述
我们可以看到在toys表中我们仅仅用owner_typeowner_id就完成了对boysgils表的区分,避免了不必要的麻烦

补充
可以使用标签 polymorphicValue 来更改多态类型的值,像下面这样:

type Boy struct {
	gorm.Model
	Name string
	Toys []Toy `gorm:"polymorphic:Owner;polymorphicValue:bbbb"`
}

type Girl struct {
	gorm.Model
	Name string
	Toys []Toy `gorm:"polymorphic:Owner;polymorphicValue:gggg"`
}

type Toy struct {
	gorm.Model
	Name      string
	OwnerID   uint
	OwnerType string
}

创建出来的表:
在这里插入图片描述

关联标签

前言

关联标签这里我们主要介绍四个:

  • foreignKey:
  • references :
  • joinForeignKey
  • joinReferences

foreignKey与references

这里我们用一对多的多表关系来解释

type Boy struct {
	gorm.Model
	Name string
	Toys []Toy `gorm:"polymorphic:Owner;foreign:Name;references:BoyName"`
}

type Toy struct {
	gorm.Model
	ToyName   string
	BoyName   string
	OwnerID   uint
	OwnerType string
}

如上面所示:
foreignKey:用来指定连接表的外键。
references:用来指定引用表的列名与连接表的外键映射。

joinForeignKey与joinReferences

joinForeignKey:指定Many to Many产生的连接表中关联外键映射字段的名称。
joinReferences:指定Many to Many产生的连接表中关联外键字段的名称。

这里的演示我们用多对多的多表关系来演示:

package main

import (
	"gorm.io/gorm"
	"gorm/ConnectDB"
)

type Girl struct {
	gorm.Model
	ToyName string
	Name    string
	Toys    []Toy `gorm:"many2many:girls_toys;foreign:ToyName;joinForeignKey:a;joinReferences:b"`
}

type Toy struct {
	gorm.Model
	ToyName string
}

func main() {
	db, err := ConnectDB.Connect()
	if err != nil {
		panic(err)
	}

	err = db.AutoMigrate(&Girl{}, &Toy{})
	if err != nil {
		panic(err)
	}

	db.Create(&Girl{
		Name: "三玖",
		Toys: []Toy{
			{ToyName: "玩具3"},
			{ToyName: "玩具4"},
		},
	})
}

它创建出来的连接表是这样的:
在这里插入图片描述
用通俗的方式来说,其实它们的作用就是决定了连接表的列名。

自定义数据类型

前言

GORM中允许我们去使用自定义的数据类型,但是我们必须要实现ScannerValue接口,以便让GORM知道如何接收并保存该类型到数据库中。

自定义结构体

package main

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"gorm/ConnectDB"
)

type User struct {
	ID   uint
	Info UserInfo
}

type UserInfo struct {
	Name string
	Age  int
}

func (u *UserInfo) Scan(value interface{}) error {
	bytes, ok := value.([]byte)
	if !ok {
		return errors.New(fmt.Sprintf("Scan failed: %v", value))
	}
	info := UserInfo{}
	err := json.Unmarshal(bytes, &info)
	*u = info
	return err
}

func (u UserInfo) Value() (driver.Value, error) {
	return json.Marshal(u)
}

func main() {
	db, err := ConnectDB.Connect()
	if err != nil {
		fmt.Println("数据库连接失败,err:", err)
		return
	}

	err = db.AutoMigrate(&User{})
	if err != nil {
		fmt.Println("表创建失败,err:", err)
		return
	}

	user := User{
		Info: UserInfo{
			Name: "张三",
			Age:  18,
		},
	}
	db.Create(&user)

	db.First(&user)
	fmt.Println(user)
}

自定义数组

func (a *Args) Scan(value interface{}) error {
	str, ok := value.([]byte)
	if !ok {
		return errors.New(fmt.Sprintf("Scan failed: %v", value))
	}
	*a = strings.Split(string(str), ",")
	return nil
}

func (a Args) Value() (driver.Value, error) {
	if len(a) > 0 {
		var str string
		str = a[0]
		for i := 1; i < len(a); i++ {
			str += "," + a[i]
		}
		return str, nil
	}
	return "", nil
}

事务

前言

事务就是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。很形象的一个例子,张三给李四转账100元,在程序里面,张三的余额就要-100,李四的余额就要+100 整个事件是一个整体,哪一步错了,整个事件都是失败的
gorm事务默认是开启的。为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,我们可以在初始化时禁用它,这将获得大约 30%+ 性能提升。但是一般不推荐禁用。

相关表结构

我们这里相关表结构

type User struct {
  ID    uint   `json:"id"`
  Name  string `json:"name"`
  Money int    `json:"money"`
}

事务的使用

现在有一个场景:,张三给李四转账100元,在程序里面,张三的余额就要-100,李四的余额就要+100

如果不使用事务,是这样的:

var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "张三")
DB.Take(&lisi, "name = ?", "李四")

// 先给张三-100
zhangsan.Money -= 100
DB.Model(&zhangsan).Update("money", zhangsan.Money)

// 再给李四+100
lisi.Money += 100
DB.Model(&lisi).Update("money", lisi.Money)

在失败的情况下,要么张三白白损失了100,要么李四凭空拿到100元这显然是不合逻辑的,并且不合法的,而这就需要我们来使用事务了,事务一共分为两种:

  • 自动事务
  • 手动事务

我们分别来看一下它们的写法:

  • 自动事务:
var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "张三")
DB.Take(&lisi, "name = ?", "李四")
// 张三给李四转账100元
DB.Transaction(func(tx *gorm.DB) error {

  // 先给张三-100
  zhangsan.Money -= 100
  err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Error
  if err != nil {
    fmt.Println(err)
    return err
  }

  // 再给李四+100
  lisi.Money += 100
  err = tx.Model(&lisi).Update("money", lisi.Money).Error
  if err != nil {
    fmt.Println(err)
    return err
  }
  // 提交事务
  return nil
})
  • 手动事务:

    • 执行流程:
    //开始事务
    tx := db.Begin()
    
    // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
    tx.Create(...)
    
    // ...
    
    // 遇到错误时回滚事务
    tx.Rollback()
    
    // 否则,提交事务
    tx.Commit()
    
var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "张三")
DB.Take(&lisi, "name = ?", "李四")

// 张三给李四转账100元
tx := DB.Begin()

// 先给张三-100
zhangsan.Money -= 100
err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Error
if err != nil {
  tx.Rollback()
}

// 再给李四+100
lisi.Money += 100
err = tx.Model(&lisi).Update("money", lisi.Money).Error
if err != nil {
  tx.Rollback()
}
// 提交事务
tx.Commit()

结语

至此,GORM的学习就告一段落了,大家下篇文章见,拜拜!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C/C++】C/C++车辆交通违章管理系统(源码+数据文件)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

数据标准的制定落地

目录 什么是数据标准 基本定义 目的 数据标准体系分类 从内容层面分类 从管理视角分类 从面向的对象分类 从数据结构的角度分类 数据标准价值 业务价值 技术价值 管理价值 数据标准和数据治理的关系 数据标准在数据治理各项任务中的作用 数据标准与主数据 数据…

【Linux】中常见的重要指令(下)以及重要的几个热键

目录 一、时间相关的指令date 1.时间戳 二、Cal指令 三、find指令 1.whereis 2.which 四、grep指令 五、zip和unzip指令 六、tar指令 七、bc指令 八、重要的几个热键[Tab]&#xff0c;[ctrl]-c&#xff0c;[ctrl]-d 一、时间相关的指令date date 指定格式显示时间&…

夜天之书 #98 Rust 程序库生态合作的例子

近期主要时间都在适应产品市场&#xff08;Product Marketing&#xff09;的新角色&#xff0c;不少想法还在酝酿和斟酌当中&#xff0c;于是文章输出没有太多时间来推敲和选题&#xff0c;只能保持每月发布相关的进展或一些零碎的思考。或许我可以恢复最早的模式&#xff0c;多…

node.js点餐系统app-计算机毕业设计源码84406

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

脚本实现登陆滑块

脚本实现登陆滑块 仅供学习参考&#xff0c;简单操作 你知道吗&#xff0c;滑动验证码居然是为了验证人类比机器人蠢而设计的。 你以为自己快速、准确地滑动拼图、对齐图案&#xff0c;才被允许通过&#xff0c;系统还说你超越了99%的用户&#xff0c;夸你“比闪电还快”&am…

算法-随机快排及荷兰国旗优化

文章目录 算法介绍 :1. 随机快排解析2. 荷兰国旗问题3. 随机快排优化4. 总结随机快排 算法介绍 : 随机快速排序和传统的快速排序的逻辑本质是一致的,都是找到一个值作为划分的中间位置,左边数值均小于该数值,右边数值均大于该数值,但是与传统的快排又不一致的是,我们的这个位置…

Chrome DevTools

Console 面板 此章节请打开 justwe7.github.io/devtools/console/console.html 一起食用 一方面用来记录页面在执行过程中的信息&#xff08;一般通过各种 console 语句来实现&#xff09;&#xff0c;另一方面用来当做 shell 窗口来执行脚本以及与页面文档、DevTools 等进行交…

动态SQL IF语句

IF语句学习 第一种写法(标准) 我们先来看以下标准写法: select * from .. <where> <if test""> and ....... <if test""> and ....... <where> 我们用了一个where标签 , 内嵌if语句 第二种写法: 这是第二种写法:不用where标…

综合交易模型--雪球跟单参数说明支持qmt,同花顺

经过测试&#xff0c;目前完成了这个策略。支持多策略&#xff0c;支持全市场&#xff0c;包括股票&#xff0c;etf,可转债 全部的参数 { "雪球跟单":"跟单原理", "原理":"比重变大默认买入&#xff0c;变小默认卖出&#xff0c;持股…

【SpringBoot】SpringBoot项目关于默认port以及context path的配置 application.yml

application.yml server port [端口号] 配置/修改默认端口号 # server configurationserver:port: 8080context path [虚拟目录] 配置/修改默认虚拟目录 # server configurationserver:servlet:context-path: /spring configuration # spring configuration spring:applica…

mysql DDL——增删改

简略版&#xff1a; 详细版&#xff1a; DDL&#xff1a;对库中表的的记录进行增删改操作&#xff1b; 分别对应&#xff1a;添加&#xff08;insert&#xff09;&#xff0c;修改(update)&#xff0c;删除(delete); 一&#xff1a;添加数据 1. 对全部字段添加数据&#x…

【一刷《剑指Offer》】面试题 28:字符串的排列

牛客对应题目链接&#xff1a;字符串的排列_牛客题霸_牛客网 (nowcoder.com) 力扣对应题目链接&#xff1a;LCR 157. 套餐内商品的排列顺序 - 力扣&#xff08;LeetCode&#xff09; 核心考点 &#xff1a;全排列问题&#xff0c; DFS。 一、《剑指Offer》对应内容 二、分析题…

关于留痕的使用常见的问题

1. 登录微信 登录要导出数据的微信&#xff08;不支持微信多开&#xff0c;不支持部分老版本微信&#xff09; 相关信息 想把手机端的微信聊天记录转移到电脑上可以使用微信自带的聊天记录迁移功能 操作步骤&#xff1a; 安卓&#xff1a; 手机微信->我->设置->聊…

AI解密:语言模型生成下一个词的概率从何而来

在这个信息爆炸的时代&#xff0c;你是否曾好奇过&#xff0c;当你与聊天机器人流畅对话时&#xff0c;那些机智回复的背后&#xff0c;究竟隐藏着怎样的秘密&#xff1f;今天&#xff0c;就让我们一起乘坐时光机&#xff0c;深入语言模型的神秘腹地&#xff0c;揭开它预测下一…

【spring】第二篇 bean实例化

对象已经能交给Spring的IOC容器来创建了&#xff0c;但是容器是如何来创建对象的呢? 就需要研究下bean的实例化过程&#xff0c;在这块内容中主要解决两部分内容&#xff0c;分别是 bean是如何创建的 实例化bean的三种方式&#xff0c;构造方法,静态工厂和实例工厂 在讲解这…

iOS——类与对象底层探索

类和对象的本质 当我们使用OC创建一个testClass类并在main函数创建它的实例对象的时候&#xff0c;OC的底层到底是什么样的呢&#xff1f; 首先&#xff0c;我们要了解OC对象的底层结构&#xff0c;那么我们就得知道&#xff1a;OC本质底层实现转化其实都是C/C代码。 使用下面…

详解 Spark SQL 核心编程知识

一、SparkSQL 概述 1. 概念 Spark SQL 是 Spark 用于结构化数据 (structured data) 处理的 Spark 模块&#xff0c;使用 SQL 的方式简化 RDD 的开发 2. Hive VS SparkSQL Hive 是早期唯一运行在 Hadoop 上的 SQL-on-Hadoop 工具&#xff0c;但是 MapReduce 计算过程中大量的中…

java高并发实战<2>

##>>> 我们解决我们重复下单的问题 我们可以使用mysql 的唯一索引 &#xff0c;在我们的数据库层面保证不能重复下单 我可以控制是唯一的 同一个用户 针对于同一个商品只可以买一个 重复下单 优化 我们 >1.使用数据库唯一索引 一旦是 2个请求 因为mysql 有行级…

万物皆有定数

前段时间&#xff0c;测算一个女孩的婚姻&#xff0c;她年底或明年必有婚姻&#xff0c;因为蛇冲猪日&#xff0c;冲动夫宫&#xff0c;就有婚姻出现。不过&#xff0c;按照她总体八字分析&#xff0c;是要晚婚的&#xff0c;但这个运已到&#xff0c;所以&#xff0c;就要允许…