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.
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 ?.
Untuk meredakan keadaan, mari kita gunakan lukisan untuk menggambarkan hubungan antara kod dan objek SQL:
Jom fokus pada setiap bekas satu persatu.
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).
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.
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 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).
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 }
Sekarang, mari kita serlahkan bahagian yang berkaitan dengannya ?:
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.
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)) }
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}');
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:
Lastly, to ensure that everything works as expected, we do a couple of things:
That's it for this file. Now, let's test our work ?.
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
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 } }
Voilà! Everything works as expected. We can re-run the code several times and always have the same output.
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!