Day09:GORM快速入门10 预加载| 青训营

752 阅读4分钟

导读

本套笔记是学习完Gin框架之后的下一个阶段,通过Gin框架我们可以很方便的和前端进行交互,下一步就是和数据库进行交互,这就需要使用GORM。本套笔记就是带你快速上手GORM,学习完这个之后就可以完成大项目的开发了,后续还会进行大项目的讲解开发,制作不易,喜欢的就点个关注吧。

本套课程基于GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.进行讲解,是对这个的补充解释说明。

注意

代码详解大部分是注释的形式给出,请留意代码注释。

GORM介绍

GORM(Go Object Relational Mapping)是一个在 Go 语言中使用的优秀的 ORM(对象关系映射)库。它提供了简单且强大的 API,使开发人员能够轻松地与数据库进行交互

预加载

预加载(Preloading)是指在执行数据库查询时,同时加载关联的关联数据,以减少后续访问数据库的次数。它可以提高查询性能,并避免 N+1 查询问题。

预加载示例

GORM允许使用 Preload通过多个SQL中来直接加载关系, 例如:

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
)

type User struct {
	gorm.Model
	Name     string
	Articles []Article
}

type Article struct {
	gorm.Model
	Title   string
	Content string
	UserID  uint
	User    User
}

func main() {
	dsn := "user:password@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal(err)
	}

	// 自动迁移数据库结构
	err = db.AutoMigrate(&User{}, &Article{})
	if err != nil {
		log.Fatal(err)
	}

	// 创建用户
	user := User{Name: "John"}
	result := db.Create(&user)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	// 创建文章,并关联用户
	article := Article{Title: "Hello", Content: "World", UserID: user.ID}
	result = db.Create(&article)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	// 查询用户及其关联的文章
	var fetchedUser User
	result = db.Preload("Articles").First(&fetchedUser, user.ID)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	fmt.Println("User:", fetchedUser)
	fmt.Println("Articles:")
	for _, article := range fetchedUser.Articles {
		fmt.Println(article)
	}
}

在上述示例中,我们使用 db.Preload("Articles").First(&fetchedUser, user.ID) 查询用户并预加载关联的文章。这样,在访问 fetchedUser.Articles 时,相关的文章数据已经被提前加载,避免了额外的数据库查询。

预加载有助于优化查询性能和减少数据库查询次数,特别适用于涉及到大量关联数据的情况。

JOIN预加载

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
)

type User struct {
	gorm.Model
	Name     string
	Articles []Article
}

type Article struct {
	gorm.Model
	Title   string
	Content string
	UserID  uint
	User    User
}

func main() {
	dsn := "user:password@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal(err)
	}

	// 自动迁移数据库结构
	err = db.AutoMigrate(&User{}, &Article{})
	if err != nil {
		log.Fatal(err)
	}

	// 创建用户
	user := User{Name: "John"}
	result := db.Create(&user)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	// 创建文章,并关联用户
	article := Article{Title: "Hello", Content: "World", UserID: user.ID}
	result = db.Create(&article)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	// 使用 Joins 预加载查询用户及其关联的文章
	var users []User
	result = db.Joins("JOIN articles ON users.id = articles.user_id").
		Distinct().
		Preload("Articles").
		Find(&users)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	for _, user := range users {
		fmt.Println("User:", user)
		fmt.Println("Articles:")
		for _, article := range user.Articles {
			fmt.Println(article)
		}
		fmt.Println("------")
	}
}

在上述示例中,我们使用 db.Joins("JOIN articles ON users.id = articles.user_id") 执行 JOIN 操作,关联用户和文章表。然后,使用 Preload("Articles") 预加载关联的文章数据。

预加载全部

如果你想要一次性预加载所有的关联数据,而不仅仅是单个关联字段,可以使用 Preload 方法并传递一个空字符串作为参数。这将会预加载模型定义中的所有关联。

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
)

type User struct {
	gorm.Model
	Name     string
	Articles []Article
}

type Article struct {
	gorm.Model
	Title   string
	Content string
	UserID  uint
	User    User
}

func main() {
	dsn := "user:password@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal(err)
	}

	// 自动迁移数据库结构
	err = db.AutoMigrate(&User{}, &Article{})
	if err != nil {
		log.Fatal(err)
	}

	// 创建用户
	user := User{Name: "John"}
	result := db.Create(&user)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	// 创建文章,并关联用户
	article := Article{Title: "Hello", Content: "World", UserID: user.ID}
	result = db.Create(&article)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	// 查询用户及其关联的所有数据
	var fetchedUser User
	result = db.Preload("").First(&fetchedUser, user.ID)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	fmt.Println("User:", fetchedUser)
	fmt.Println("Articles:")
	for _, article := range fetchedUser.Articles {
		fmt.Println(article)
	}
}

在上述示例中,我们使用 db.Preload("") 查询用户并预加载所有的关联数据。这样,当访问 fetchedUser.Articles 时,所有相关的文章数据都已经被提前加载。

通过使用空字符串作为参数,可以一次性预加载模型定义中的所有关联字段。

条件预加载

GORM允许预加载与条件关联,它的工作原理类似于内联条件

// 使用条件预加载订单数据 db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users) 
// 查询用户数据:
SELECT * FROM users; 
// 查询订单数据:
SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');

db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users) 
// 查询状态为 "active" 的用户数据:SELECT * FROM users WHERE state = 'active'; 
// 查询订单数据:SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');