」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > Gorm:自訂資料類型先睹為快

Gorm:自訂資料類型先睹為快

發佈於2024-11-08
瀏覽:815

欢迎回来,伙计们?!今天,我们讨论在数据库之间来回移动数据时可能遇到的一个特定用例。首先,让我为今天的挑战设定界限。为了坚持现实生活中的例子,让我们借用美国陆军的一些概念?我们的任务是编写一个小软件来保存和读取军官在职业生涯中取得的成绩。

Gorm 的自定义数据类型

我们的软件需要处理军官及其各自的等级。乍一看,这似乎很简单,而且我们可能不需要任何自定义数据类型。但是,为了展示此功能,让我们使用非常规方式来表示数据。因此,我们被要求定义 Go 结构和数据库关系之间的自定义映射。此外,我们必须定义特定的逻辑来解析数据。让我们通过查看该计划的目标来扩展这一点?.

要处理的用例

为了方便起见,我们用一张图来描述代码和 SQL 对象之间的关系:

Gorm: Sneak Peek of Custom Data Types

让我们逐一关注每个容器。

Go 结构?

在这里,我们定义了两个结构体。 Grade 结构体包含军事等级的非详尽列表?️。该结构不会是数据库中的表。相反,Officer 结构体包含 ID、姓名和指向 Grade 结构体的指针,指示该军官到目前为止已取得的成绩。

每当我们向数据库写入官员时,grades_achieved 列必须包含一个字符串数组,其中填充了所达到的成绩(Grade 结构中具有 true 的成绩)。

数据库关系?

关于 SQL 对象,我们只有军官表。 id 和 name 列是不言自明的。然后,我们有 Grades_achieved 列,将官员的成绩保存在字符串集合中。

每当我们从数据库中解码官员时,我们都会解析 Grades_achieved 列并创建 Grade 结构的匹配“实例”。

您可能已经注意到这种行为不是标准行为。我们必须做出一些安排,以期望的方式实现它。

这里,模型的布局故意过于复杂。请尽可能坚持使用更简单的解决方案。

自定义数据类型

Gorm 为我们提供了自定义数据类型。它们为我们定义数据库的检索和保存提供了极大的灵活性。我们必须实现两个接口:Scanner 和 Valuer?。前者指定从数据库获取数据时要应用的自定义行为。后者指示如何将值写入数据库。两者都帮助我们实现我们需要的非常规映射逻辑。

我们必须实现的函数签名是 Scan(value interface{}) error 和 Value() (driver.Value, error)。现在,让我们看一下代码。

守则

此示例的代码位于两个文件中:domain/models.go 和 main.go。让我们从第一个开始,处理模型(在 Go 中翻译为结构体)。

域/models.go 文件

首先,让我展示这个文件的代码:

package models

import (
 "database/sql/driver"
 "slices"
 "strings"
)

type Grade struct {
 Lieutenant bool
 Captain    bool
 Colonel    bool
 General    bool
}

type Officer struct {
 ID             uint64 `gorm:"primaryKey"`
 Name           string
 GradesAchieved *Grade `gorm:"type:varchar[]"`
}

func (g *Grade) Scan(value interface{}) error {
 // we should have utilized the "comma, ok" idiom
 valueRaw := value.(string)
 valueRaw = strings.Replace(strings.Replace(valueRaw, "{", "", -1), "}", "", -1)
 grades := strings.Split(valueRaw, ",")
 if slices.Contains(grades, "lieutenant") {
 g.Lieutenant = true
 }
 if slices.Contains(grades, "captain") {
 g.Captain = true
 }
 if slices.Contains(grades, "colonel") {
 g.Colonel = true
 }
 if slices.Contains(grades, "general") {
 g.General = true
 }
 return nil
}

func (g Grade) Value() (driver.Value, error) {
 grades := make([]string, 0, 4)
 if g.Lieutenant {
 grades = append(grades, "lieutenant")
 }
 if g.Captain {
 grades = append(grades, "captain")
 }
 if g.Colonel {
 grades = append(grades, "colonel")
 }
 if g.General {
 grades = append(grades, "general")
 }
 return grades, nil
}

现在,让我们突出显示它的相关部分?:

  1. Grade 结构仅列出我们在软件中预测的成绩
  2. Officer 结构定义了实体的特征。该实体是数据库中的关系。我们应用了两种 Gorm 符号:
    1. gorm:ID 字段上的“primaryKey”将其定义为我们关系的主键
    2. gorm:"type:varchar[]" 将字段 GradesAchieved 映射为数据库中的 varchar 数组。否则,它会转换为单独的数据库表或官员表中的附加列
  3. Grade 结构体实现 Scan 函数。在这里,我们获取原始值,调整它,在 g 变量上设置一些字段,然后返回
  4. Grade 结构体还将 Value 函数实现为值接收器类型(这次我们不需要更改接收器,我们不使用 * 引用)。我们返回要写入军官表的 Grades_achieved 列中的值

借助这两种方法,我们可以控制在数据库交互期间如何发送和检索类型 Grade。现在,让我们看一下 main.go 文件。

main.go 文件?

在这里,我们准备数据库连接,将对象迁移到关系(ORM代表Object Relation Mapping),然后插入和获取记录下来测试逻辑。代码如下:

package main

import (
 "encoding/json"
 "fmt"
 "os"

 "gormcustomdatatype/models"

 "gorm.io/driver/postgres"
 "gorm.io/gorm"
)

func seedDB(db *gorm.DB, file string) error {
 data, err := os.ReadFile(file)
 if err != nil {
  return err
 }
 if err := db.Exec(string(data)).Error; err != nil {
  return err
 }
 return nil
}

// docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres
func main() {
 dsn := "host=localhost port=54322 user=postgres password=postgres dbname=postgres sslmode=disable"
 db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
 if err != nil {
 fmt.Fprintf(os.Stderr, "could not connect to DB: %v", err)
  return
 }
 db.AutoMigrate(&models.Officer{})
 defer func() {
 db.Migrator().DropTable(&models.Officer{})
 }()
 if err := seedDB(db, "data.sql"); err != nil {
 fmt.Fprintf(os.Stderr, "failed to seed DB: %v", err)
  return
 }
 // print all the officers
 var officers []models.Officer
 if err := db.Find(&officers).Error; err != nil {
 fmt.Fprintf(os.Stderr, "could not get the officers from the DB: %v", err)
  return
 }
 data, _ := json.MarshalIndent(officers, "", "\t")
 fmt.Fprintln(os.Stdout, string(data))

 // add a new officer
 db.Create(&models.Officer{
 Name: "Monkey D. Garp",
 GradesAchieved: &models.Grade{
 Lieutenant: true,
 Captain:    true,
 Colonel:    true,
 General:    true,
  },
 })
 var garpTheHero models.Officer
 if err := db.First(&garpTheHero, 4).Error; err != nil {
 fmt.Fprintf(os.Stderr, "failed to get officer from the DB: %v", err)
  return
 }
 data, _ = json.MarshalIndent(&garpTheHero, "", "\t")
 fmt.Fprintln(os.Stdout, string(data))
}

现在,让我们看看这个文件的相关部分。首先,我们定义seedDB函数来在数据库中添加虚拟数据。数据位于 data.sql 文件中,内容如下:

INSERT INTO public.officers
(id, "name", grades_achieved)
VALUES(nextval('officers_id_seq'::regclass), 'john doe', '{captain,lieutenant}'),
(nextval('officers_id_seq'::regclass), 'gerard butler', '{general}'),
(nextval('officers_id_seq'::regclass), 'chuck norris', '{lieutenant,captain,colonel}');

main() 函数首先设置数据库连接。在本演示中,我们使用了 PostgreSQL。然后,我们确保军官表存在于数据库中,并且与最新版本的 models.Officer 结构保持同步。由于该程序是一个示例,因此我们做了两件事:

  • 在main()函数末尾删除表(当程序终止时,我们也想删除表)
  • 一些虚拟数据的播种

最后,为了确保一切按预期进行,我们做了几件事:

  1. 获取数据库中的所有记录
  2. 添加(并取回)新官员

这个文件就是这样。现在,让我们测试一下我们的工作?.

真相时刻

在运行代码之前,请确保您的计算机上正在运行 PostgreSQL 实例。使用 Docker ?,你可以运行这个命令:

docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres

现在,我们可以通过发出命令来安全地运行我们的应用程序: go run 。 ?

输出为:

[
        {
                "ID": 1,
                "Name": "john doe",
                "GradesAchieved": {
                        "Lieutenant": true,
                        "Captain": true,
                        "Colonel": false,
                        "General": false
                }
        },
        {
                "ID": 2,
                "Name": "gerard butler",
                "GradesAchieved": {
                        "Lieutenant": false,
                        "Captain": false,
                        "Colonel": false,
                        "General": true
                }
        },
        {
                "ID": 3,
                "Name": "chuck norris",
                "GradesAchieved": {
                        "Lieutenant": true,
                        "Captain": true,
                        "Colonel": true,
                        "General": false
                }
        }
]
{
        "ID": 4,
        "Name": "Monkey D. Garp",
        "GradesAchieved": {
                "Lieutenant": true,
                "Captain": true,
                "Colonel": true,
                "General": true
        }
}

瞧!一切都按预期进行。我们可以多次重新运行代码并始终得到相同的输出。

这是一个包裹

我希望您喜欢这篇关于 Gorm自定义数据类型 的博文。我始终建议您坚持使用最直接的方法。仅当您最终需要时才选择此选项。这种方法增加了灵活性,但代价是使代码变得更复杂和更不健壮(结构定义的微小变化可能会导致错误和需要额外的工作)。

记住这一点。如果您坚持约定,则整个代码库中的内容就会变得不那么冗长。

这是结束这篇博文的一个很好的引用。
如果您意识到需要自定义数据类型,这篇博文应该是一个很好的起点,可以为您提供一个可行的解决方案。

请告诉我您的感受和想法。任何反馈总是值得赞赏!如果您对某个特定主题感兴趣,请联系我,我会将其列入候选名单。下次见,注意安全,再见!

版本聲明 本文轉載於:https://dev.to/ossan/gorm-sneak-peek-of-custom-data-types-97n?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • eval()vs. ast.literal_eval():對於用戶輸入,哪個Python函數更安全?
    eval()vs. ast.literal_eval():對於用戶輸入,哪個Python函數更安全?
    稱量()和ast.literal_eval()中的Python Security 在使用用戶輸入時,必須優先確保安全性。強大的Python功能Eval()通常是作為潛在解決方案而出現的,但擔心其潛在風險。本文深入研究了eval()和ast.literal_eval()之間的差異,突出顯示其安全性含義...
    程式設計 發佈於2025-05-05
  • Java中假喚醒真的會發生嗎?
    Java中假喚醒真的會發生嗎?
    在Java中的浪費喚醒:真實性或神話? 在Java同步中偽裝喚醒的概念已經是討論的主題。儘管存在這種行為的潛力,但問題仍然存在:它們實際上是在實踐中發生的嗎? Linux的喚醒機制根據Wikipedia關於偽造喚醒的文章,linux實現了pthread_cond_wait()功能的Linux實現,...
    程式設計 發佈於2025-05-05
  • 如何有效地轉換PHP中的時區?
    如何有效地轉換PHP中的時區?
    在PHP 利用dateTime對象和functions DateTime對象及其相應的功能別名為時區轉換提供方便的方法。例如: //定義用戶的時區 date_default_timezone_set('歐洲/倫敦'); //創建DateTime對象 $ dateTime = ne...
    程式設計 發佈於2025-05-05
  • 如何正確使用與PDO參數的查詢一樣?
    如何正確使用與PDO參數的查詢一樣?
    在pdo 中使用類似QUERIES在PDO中的Queries時,您可能會遇到類似疑問中描述的問題:此查詢也可能不會返回結果,即使$ var1和$ var2包含有效的搜索詞。錯誤在於不正確包含%符號。 通過將變量包含在$ params數組中的%符號中,您確保將%字符正確替換到查詢中。沒有此修改,PD...
    程式設計 發佈於2025-05-05
  • 您如何在Laravel Blade模板中定義變量?
    您如何在Laravel Blade模板中定義變量?
    在Laravel Blade模板中使用Elegance 在blade模板中如何分配變量對於存儲以後使用的數據至關重要。在使用“ {{}}”分配變量的同時,它可能並不總是最優雅的解決方案。 幸運的是,Blade通過@php Directive提供了更優雅的方法: $ old_section =...
    程式設計 發佈於2025-05-05
  • 在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在JTable中維護jtable單元格渲染後,在JTable中,在JTable中實現自定義單元格渲染和編輯功能可以增強用戶體驗。但是,至關重要的是要確保即使在編輯操作後也保留所需的格式。 在設置用於格式化“價格”列的“價格”列,用戶遇到的數字格式丟失的“價格”列的“價格”之後,問題在設置自定義單元...
    程式設計 發佈於2025-05-05
  • 為什麼我會收到MySQL錯誤#1089:錯誤的前綴密鑰?
    為什麼我會收到MySQL錯誤#1089:錯誤的前綴密鑰?
    mySQL錯誤#1089:錯誤的前綴鍵錯誤descript [#1089-不正確的前綴鍵在嘗試在表中創建一個prefix鍵時會出現。前綴鍵旨在索引字符串列的特定前綴長度長度,以便更快地搜索這些前綴。 理解prefix keys `這將在整個Movie_ID列上創建標準主鍵。主密鑰對於唯一識...
    程式設計 發佈於2025-05-05
  • 在Java中使用for-to-loop和迭代器進行收集遍歷之間是否存在性能差異?
    在Java中使用for-to-loop和迭代器進行收集遍歷之間是否存在性能差異?
    For Each Loop vs. Iterator: Efficiency in Collection TraversalIntroductionWhen traversing a collection in Java, the choice arises between using a for-...
    程式設計 發佈於2025-05-05
  • MySQL中如何高效地根據兩個條件INSERT或UPDATE行?
    MySQL中如何高效地根據兩個條件INSERT或UPDATE行?
    在兩個條件下插入或更新或更新 solution:的答案在於mysql的插入中...在重複鍵更新語法上。如果不存在匹配行或更新現有行,則此功能強大的功能可以通過插入新行來進行有效的數據操作。如果違反了唯一的密鑰約束。 實現所需的行為,該表必須具有唯一的鍵定義(在這種情況下為'名稱'...
    程式設計 發佈於2025-05-05
  • \“(1)vs.(;;):編譯器優化是否消除了性能差異?\”
    \“(1)vs.(;;):編譯器優化是否消除了性能差異?\”
    答案: 在大多數現代編譯器中,while(1)和(1)和(;;)之間沒有性能差異。編譯器: perl: 1 輸入 - > 2 2 NextState(Main 2 -E:1)V-> 3 9 Leaveloop VK/2-> A 3 toterloop(next-> 8 last-> 9 ...
    程式設計 發佈於2025-05-05
  • 表單刷新後如何防止重複提交?
    表單刷新後如何防止重複提交?
    在Web開發中預防重複提交 在表格提交後刷新頁面時,遇到重複提交的問題是常見的。要解決這個問題,請考慮以下方法: 想像一下具有這樣的代碼段,看起來像這樣的代碼段:)){ //數據庫操作... 迴聲“操作完成”; 死(); } ? > ...
    程式設計 發佈於2025-05-05
  • 如何從2D數組中提取元素?使用另一數組的索引
    如何從2D數組中提取元素?使用另一數組的索引
    Using NumPy Array as Indices for the 2nd Dimension of Another ArrayTo extract specific elements from a 2D array based on indices provided by a second ...
    程式設計 發佈於2025-05-05
  • 如何使用Depimal.parse()中的指數表示法中的數字?
    如何使用Depimal.parse()中的指數表示法中的數字?
    在嘗試使用Decimal.parse(“ 1.2345e-02”中的指數符號表示法表示的字符串時,您可能會遇到錯誤。這是因為默認解析方法無法識別指數符號。 成功解析這樣的字符串,您需要明確指定它代表浮點數。您可以使用numbersTyles.Float樣式進行此操作,如下所示:[&& && && ...
    程式設計 發佈於2025-05-05
  • Java開發者如何保護數據庫憑證免受反編譯?
    Java開發者如何保護數據庫憑證免受反編譯?
    在java 在單獨的配置文件保護數據庫憑證的最有效方法中存儲憑據是將它們存儲在單獨的配置文件中。該文件可以在運行時加載,從而使登錄數據從編譯的二進製文件中遠離。 使用prevereness class import java.util.prefs.preferences; 公共類示例{ 首選...
    程式設計 發佈於2025-05-05
  • 如何使用替換指令在GO MOD中解析模塊路徑差異?
    如何使用替換指令在GO MOD中解析模塊路徑差異?
    在使用GO MOD時,在GO MOD 中克服模塊路徑差異時,可能會遇到衝突,其中可能會遇到一個衝突,其中3派對軟件包將另一個帶有導入套件的path package the Imptioned package the Imptioned package the Imported tocted pac...
    程式設計 發佈於2025-05-05

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3