Go語言之GORM框架(四)——預加載,關聯標簽與多態關聯,自定義數據類型與事務(完結篇)

前言

本來是想著寫多表關系的,不過寫了一半發現重復的部分太多了,想了想與其做一些重復性工作,不如把一些當時覺得抽象的東西記錄一下,就當用一篇雜記完成專欄的最后一篇文章吧。

預加載

簡單示例

預加載主要用于在多表關系中加載關聯表的信息,在講解預加載的類型之前我們先來看一個預加載的示例:

  • 相關表結構
type User struct {gorm.ModelName      stringLanguages []Language `gorm:"many2many:user_languages;"`
}type Language struct {gorm.ModelName  stringUsers []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     *stringStudentID uint `gorm:"size:8"`
}
  • 示例:
	var student Studentdb.Joins("Info").Take(&student)db.Joins("Info", db.Where("Info.Age = ?", 18)) //帶條件的Joinsfmt.Println(student)

條件預加載

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

自定義預加載

	var student Studentdb.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 mainimport ("gorm.io/gorm""gorm/ConnectDB"
)type Boy struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner"`
}type Girl struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner"`
}type Toy struct {gorm.ModelName      stringOwnerID   uintOwnerType 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.ModelName stringToys []Toy `gorm:"polymorphic:Owner;polymorphicValue:bbbb"`
}type Girl struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner;polymorphicValue:gggg"`
}type Toy struct {gorm.ModelName      stringOwnerID   uintOwnerType string
}

創建出來的表:
在這里插入圖片描述

關聯標簽

前言

關聯標簽這里我們主要介紹四個:

  • foreignKey:
  • references :
  • joinForeignKey
  • joinReferences

foreignKey與references

這里我們用一對多的多表關系來解釋

type Boy struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner;foreign:Name;references:BoyName"`
}type Toy struct {gorm.ModelToyName   stringBoyName   stringOwnerID   uintOwnerType string
}

如上面所示:
foreignKey:用來指定連接表的外鍵。
references:用來指定引用表的列名與連接表的外鍵映射。

joinForeignKey與joinReferences

joinForeignKey:指定Many to Many產生的連接表中關聯外鍵映射字段的名稱。
joinReferences:指定Many to Many產生的連接表中關聯外鍵字段的名稱。

這里的演示我們用多對多的多表關系來演示:

package mainimport ("gorm.io/gorm""gorm/ConnectDB"
)type Girl struct {gorm.ModelToyName stringName    stringToys    []Toy `gorm:"many2many:girls_toys;foreign:ToyName;joinForeignKey:a;joinReferences:b"`
}type Toy struct {gorm.ModelToyName 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 mainimport ("database/sql/driver""encoding/json""errors""fmt""gorm/ConnectDB"
)type User struct {ID   uintInfo UserInfo
}type UserInfo struct {Name stringAge  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 = inforeturn 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 stringstr = 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 {// 先給張三-100zhangsan.Money -= 100err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Errorif err != nil {fmt.Println(err)return err}// 再給李四+100lisi.Money += 100err = tx.Model(&lisi).Update("money", lisi.Money).Errorif 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的學習就告一段落了,大家下篇文章見,拜拜!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/21106.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/21106.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/21106.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

谷歌瀏覽器的平替,內置開掛神器,我已愛不釋手!

油猴瀏覽器正式版是一款基于谷歌Chromium源碼開發的瀏覽器&#xff0c;它集成了集成了強大的油猴擴展&#xff08;Tampermonkey&#xff09;&#xff0c;使得用戶可以輕松安裝各種腳本&#xff0c;從而增強網頁瀏覽體驗。提供了一個更加個性化和高效的瀏覽體驗。 油猴擴展&…

git使用流程

1.下載git 搜索下載 2.注冊github賬號&#xff08;打開爬墻工具&#xff09; 創建一個倉庫 3.配置郵箱和密碼 4.所以找一個文件夾 鼠標右鍵 選擇 open Git Bash here&#xff08;當前文件夾下打開命令行&#xff09; 輸入命令 配置用戶名和郵箱 5.將建的倉庫克隆下來 …

【JS實戰案例匯總——不定時更新版】

一&#xff1a;轉換時間案例 1 需求&#xff1a; 用戶輸入秒數&#xff0c;系統會自動將秒數轉變為小時、分鐘、秒&#xff0c;并且不滿10的要在前面補零 2 算法&#xff1a; 小時:hour parseInt(總秒數/60/60%24) 分鐘:minute parseInt(總秒數/60%60) 秒數:second pa…

測試基礎09:缺陷(bug)生命周期、定位方式和管理規范

課程大綱 1、缺陷&#xff08;bug&#xff09;生命周期 2、缺陷&#xff08;bug&#xff09;提交規范 2.1 宗旨 簡潔、清晰、可視化&#xff0c;減少溝通成本。 2.2 bug格式和內容 ① 標題&#xff1a;一級功能-二級功能-三級功能_&#xff08;一句話描述bug&#xff1a;&…

---初始Linux---

一、認識計算機 計算機 硬件 軟件 硬件&#xff1a;就是計算機系統中由電子、機械和光電元件等組成的各種物理裝置的總稱&#xff08;CPU\GPU\...&#xff09; 軟件&#xff1a;是用戶和計算機硬件之間及進行交流的工具 然而一個簡單的計算機或者說基本的計算機就是有兩大…

浙江大學數據結構MOOC-課后習題-第十講-排序5 PAT Judge【未完成】

題目匯總 浙江大學數據結構MOOC-課后習題-拼題A-代碼分享-2024 題目描述 這段文字是關于如何生成PAT&#xff08;一種編程能力測試&#xff09;的排行榜的說明。下面是這段文字的中文翻譯&#xff1a; 輸入說明&#xff1a; 每個輸入文件包含一個測試案例。對于每個案例&…

C++ A (1020) : 冪運算

文章目錄 一、題目描述二、參考代碼 一、題目描述 二、參考代碼 #include<bits/stdc.h> using namespace std; typedef long long ll;void qq(ll a, ll b, ll m) {if (a 0) cout << 0 << endl;;ll out 1;a % m;while (b > 0){if (b & 1)//奇數的最…

[AIGC] Vue2與Vue3的主要區別和示例代碼

Vue3是Vue框架的最新版本&#xff0c;它在性能、開發體驗和代碼體積等方面都有很大的改進。接下來我們將通過比較Vue2和Vue3的主要區別&#xff0c;進一步理解這些改變是如何影響我們的。 文章目錄 一、性能提升二、Composition API三、更好的類型支持四、生命周期鉤子函數變化…

lux和ffmpeg進行下載各大主流自媒體平臺視頻

1、lux下載&#xff0c;鏈接&#xff1a;https://pan.baidu.com/s/1WjGbouL3KFTU6LeqZmACpA?pwdagpp 提取碼&#xff1a;agpp 2、ffmpeg下載&#xff0c;跟lux放在同一個目錄&#xff1b; 3、為lux、ffmpeg設置環境變量&#xff1b; 4、WINR&#xff0c;打開運行&#xff0…

帶你自學大語言模型系列 —— 前言

今天開始&#xff0c;我計劃開啟一個系列 《帶你自學大語言模型》&#xff0c;內容也已經準備了一段時間了。 該系列的落腳點是“自學”和“大語言模型”&#xff0c;二者不分伯仲&#xff0c;這也是本系列和其他技術文章不一樣的地方。 至于原因&#xff0c;我不想只做大語言…

【C++】STL中vector常見功能的模擬實現

前言&#xff1a;在上一篇中我們講到了Vector的一些常見功能的使用方式&#xff0c;今天為了進一步的去學習Vector和能夠更深度的去理解Vector的一些底層的原理。 &#x1f496; 博主CSDN主頁:衛衛衛的個人主頁 &#x1f49e; &#x1f449; 專欄分類:高質量&#xff23;學習 &…

鴻蒙ArkTS聲明式開發:跨平臺支持列表【禁用控制】 通用屬性

禁用控制 組件是否可交互&#xff0c;可交互狀態下響應[點擊事件]、[觸摸事件]、[拖拽事件]、[按鍵事件]、[焦點事件]和[鼠標事件]。 說明&#xff1a; 開發前請熟悉鴻蒙開發指導文檔&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md點擊或者復制轉到…

【一刷《劍指Offer》】面試題 30:最小的 k 個數

牛客對應題目鏈接&#xff1a;最小的K個數_牛客題霸_牛客網 (nowcoder.com) 力扣對應題目鏈接&#xff1a;LCR 159. 庫存管理 III - 力扣&#xff08;LeetCode&#xff09; 核心考點 &#xff1a; topK 問題。 一、《劍指Offer》內容 二、分析題目 1、排序&#xff08;O(Nlo…

接口interfance的基本使用

一.為什么有接口&#xff1f; 接口:就是一種規則。 二.接口的定義和使用 1.接口用關鍵字interface來定義 public interface 接口名{} 2.接口不能實例化 3.接口和類之間是實現關系,通過implements關鍵字表示 4.接口的子類(實現類) 注意1&#xff1a; 接口和類的實現關系…

43.自定義線程池(一)

ThreadPool是線程池&#xff0c;里面是一定數量的線程&#xff0c;是消費者。 BlockingQueue阻塞隊列&#xff0c;線程池中的線程會從阻塞隊列中去拿任務執行。任務多了線程池處理不過來了&#xff0c;就會到Blocking Queue中排隊&#xff0c;等待執行。鏈表結構&#xff0c;特…

Netfilter/iptables

1. Netfilter組件圖 https://en.wikipedia.org/wiki/Netfilter 其中&#xff1a; etables作用于數據鏈路層&#xff0c;arptables針對ARP, iptables/ip6tables針對IP層。 nftables 是新的包過濾組件. nft是相對應的新的用戶態組件&#xff0c;用于替換etables,arptables,ipt…

從tensorflow導入EarlyStopping能運行但是一直提示未解析

在pycharm中導入早停機的庫時&#xff0c;碰上一個問題 from tensorflow.keras.callbacks import EarlyStopping這一條代碼中&#xff0c;EarlyStopping一直有個紅色波浪線&#xff0c;代表著找不到這個庫&#xff0c;提示未解析啥的。 但是運行是可以運行的&#xff0c;雖然可…

GPT-4o如何重塑AI未來!

如何評價GPT-4o? 簡介&#xff1a;最近&#xff0c;GPT-4o橫空出世。對GPT-4o這一人工智能技術進行評價&#xff0c;包括版本間的對比分析、GPT-4o的技術能力以及個人感受等。 GPT-4o似乎是一個針對GPT-4模型進行優化的版本&#xff0c;它在性能、準確性、資源效率以及安全和…

Anolis OS 8.9安裝Linux 服務器運維管理面板“1Panel”

一、簡介 1.Linux 服務器運維管理面板“1Panel” 使用go語言編寫 2.很多的項目的應用都是采用 docker 技術來實現&#xff0c;這讓 Linux 服務器的運維管理更簡單、更安全。 3.1Panel 采納最新的前端技術&#xff0c;并通過精心設計的UX 交互&#xff0c;為用戶提供更好的用戶…

Linux系統tab鍵無法補齊命令-已解決

在CentOS中&#xff0c;按下tab鍵就可以自動補全&#xff0c;但是在最小化安裝時&#xff0c;沒有安裝自動補全的包&#xff0c;需要安裝一個包才能解決 bash-completion 1.檢查是否安裝tab補齊軟件包&#xff08;如果是最小化安裝&#xff0c;默認沒有&#xff09; rpm -q ba…