Rumah > pembangunan bahagian belakang > Golang > Gorm: Tinjau Menyelinap Jenis Data Tersuai

Gorm: Tinjau Menyelinap Jenis Data Tersuai

DDD
Lepaskan: 2024-09-13 20:15:36
asal
862 orang telah melayarinya

Selamat kembali, kawan-kawan?! Hari ini, kita membincangkan kes penggunaan khusus yang mungkin kita hadapi apabila memindahkan data ke sana ke mari dari/ke pangkalan data. Pertama, izinkan saya menetapkan sempadan untuk cabaran hari ini. Untuk berpegang kepada contoh kehidupan sebenar, mari kita meminjam beberapa konsep daripada Tentera A.S.?. Perjanjian kami adalah untuk menulis perisian kecil untuk menyimpan dan membaca pegawai dengan gred yang telah mereka capai dalam kerjaya mereka.

Jenis Data Tersuai Gorm

Perisian kami perlu mengendalikan pegawai tentera dengan gred masing-masing. Pada pandangan pertama, ia mungkin kelihatan mudah, dan kami mungkin tidak memerlukan sebarang Jenis Data Tersuai di sini. Walau bagaimanapun, untuk menunjukkan ciri ini, mari gunakan cara bukan konvensional untuk mewakili data. Terima kasih kepada ini, kami diminta untuk menentukan pemetaan tersuai antara struct Go dan hubungan DB. Tambahan pula, kita mesti menentukan logik khusus untuk menghuraikan data. Mari kita kembangkan perkara ini dengan melihat sasaran program ?.

Gunakan Sarung untuk Mengendalikan

Untuk meredakan keadaan, mari kita gunakan lukisan untuk menggambarkan hubungan antara kod dan objek SQL:

Gorm: Sneak Peek of Custom Data Types

Jom fokus pada setiap bekas satu persatu.

The Go Structs ?

Di sini, kami menentukan dua struct. Struktur Gred mempunyai senarai gred tentera yang tidak lengkap ?️. Struktur ini tidak akan menjadi jadual dalam pangkalan data. Sebaliknya, struct Pegawai mengandungi ID, nama dan penunjuk kepada struct Gred, yang menunjukkan gred yang telah dicapai oleh pegawai setakat ini.

Setiap kali kami menulis pegawai kepada DB, lajur gred_capaian mesti mengandungi tatasusunan rentetan yang diisi dengan gred yang dicapai (yang benar dalam struktur Gred).

Hubungan DB?

Mengenai objek SQL, kami hanya mempunyai jadual pegawai. Lajur id dan nama adalah jelas. Kemudian, kami mempunyai lajur gred_capaian yang menyimpan gred pegawai dalam koleksi rentetan.

Setiap kali kami menyahkod pegawai daripada pangkalan data, kami menghuraikan lajur grades_achieved dan mencipta "contoh" padanan struktur Gred.

Anda mungkin perasan bahawa tingkah laku itu bukan yang standard. Kita mesti membuat beberapa pengaturan untuk memenuhinya dengan cara yang diingini.

Di sini, reka letak model sengaja dibuat terlalu rumit. Sila berpegang kepada penyelesaian yang lebih mudah apabila boleh.

Jenis Data Tersuai

Gorm memberikan kami Jenis Data Tersuai. Mereka memberi kami fleksibiliti yang hebat dalam menentukan perolehan semula dan menyimpan ke/dari pangkalan data. Kita mesti melaksanakan dua antara muka: Pengimbas dan Penilai ?. Yang pertama menentukan tingkah laku tersuai untuk digunakan semasa mengambil data daripada DB. Yang terakhir menunjukkan cara menulis nilai dalam pangkalan data. Kedua-duanya membantu kami dalam mencapai logik pemetaan bukan konvensional yang kami perlukan.

Tandatangan fungsi yang mesti kami laksanakan ialah ralat Scan(antara muka nilai{}) dan Value() (driver.Value, ralat). Sekarang, mari lihat kodnya.

Kod

Kod untuk contoh ini tinggal dalam dua fail: domain/models.go dan main.go. Mari kita mulakan dengan yang pertama, berurusan dengan model (diterjemahkan sebagai struct dalam Go).

Fail domain/models.go

Pertama, izinkan saya membentangkan kod untuk fail ini:

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
}
Salin selepas log masuk

Sekarang, mari kita serlahkan bahagian yang berkaitan dengannya ?:

  1. Struktur Gred hanya menyenaraikan gred yang kami ramalkan dalam perisian kami
  2. Struktur Pegawai mentakrifkan ciri-ciri entiti. Entiti ini ialah hubungan dalam DB. Kami menggunakan dua notasi Gorm:
    1. gorm:"primaryKey" pada medan ID untuk mentakrifkannya sebagai kunci utama perhubungan kami
    2. gorm:"type:varchar[]" untuk memetakan medan GradesAchieved sebagai tatasusunan varchar dalam DB. Jika tidak, ia diterjemahkan sebagai jadual DB yang berasingan atau lajur tambahan dalam jadual pegawai
  3. Struktur Gred melaksanakan fungsi Imbasan. Di sini, kami mendapat nilai mentah, kami melaraskannya, kami menetapkan beberapa medan pada pembolehubah g, dan kami kembali
  4. Struktur Gred juga melaksanakan fungsi Nilai sebagai jenis penerima nilai (kami tidak perlu menukar penerima kali ini, kami tidak menggunakan rujukan *). Kami mengembalikan nilai untuk ditulis dalam gred lajur_yang dicapai dalam jadual pegawai

Terima kasih kepada dua kaedah ini, kami boleh mengawal cara menghantar dan mendapatkan semula jenis Gred semasa interaksi DB. Sekarang, mari lihat fail main.go.

Fail main.go ?

Di sini, kami menyediakan sambungan DB, memindahkan objek kepada hubungan (ORM bermaksud Object Relation Mapping), dan masukkan serta ambil rekod untuk menguji logik. Di bawah ialah kod:

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))
}
Salin selepas log masuk

Now, let's see the relevant sections of this file. First, we define the seedDB function to add dummy data in the DB. The data lives in the data.sql file with the following content:

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}');
Salin selepas log masuk

The main() function starts by setting up a DB connection. For this demo, we used PostgreSQL. Then, we ensure the officers table exists in the database and is up-to-date with the newest version of the models.Officer struct. Since this program is a sample, we did two additional things:

  • Removal of the table at the end of the main() function (when the program terminates, we would like to remove the table as well)
  • Seeding of some dummy data

Lastly, to ensure that everything works as expected, we do a couple of things:

  1. Fetching all the records in the DB
  2. Adding (and fetching back) a new officer

That's it for this file. Now, let's test our work ?.

The Truth Moment

Before running the code, please ensure that a PostgreSQL instance is running on your machine. With Docker ?, you can run this command:

docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres
Salin selepas log masuk

Now, we can safely run our application by issuing the command: go run . ?

The output is:

[
        {
                "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
        }
}
Salin selepas log masuk

Voilà! Everything works as expected. We can re-run the code several times and always have the same output.

That's a Wrap

I hope you enjoyed this blog post regarding Gorm and the Custom Data Types. I always recommend you stick to the most straightforward approach. Opt for this only if you eventually need it. This approach adds flexibility in exchange for making the code more complex and less robust (a tiny change in the structs' definitions might lead to errors and extra work needed).

Keep this in mind. If you stick to conventions, you can be less verbose throughout your codebase.

That's a great quote to end this blog post.
If you realize that Custom Data Types are needed, this blog post should be a good starting point to present you with a working solution.

Please let me know your feelings and thoughts. Any feedback is always appreciated! If you're interested in a specific topic, reach out, and I'll shortlist it. Until next time, stay safe, and see you soon!

Atas ialah kandungan terperinci Gorm: Tinjau Menyelinap Jenis Data Tersuai. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan