DEV Community

Weerasak Chongnguluam
Weerasak Chongnguluam

Posted on

ตัวอย่างการสร้าง Many to Many Association ผ่าน GORM

GORM เป็น Go ORM ช่วยให้เรา Map ระหว่างโครงสร้างของ Column ใน Table ของ Database กับ Field ของ Struct ใน Go ได้

ตัวอย่างโพสต์นี้เราจะให้เห็นการ Map ของโครงสร้างที่มีความสัมพันธ์กันแบบ Many to Many เช่นเรามี table actors และมี table films ซึ่งแต่ละ record ของ actors จะโยงกับ films ได้หลายเรื่อง และ films เองก็โยงกับ actors ได้หลาย actors ซึ่งการเก็บข้อมูลสำหรับความสัมพันธ์แบบนี้ เราจะสร้างอีก table เพื่อโยง primary key ของ actors กับ primary key ของ films เข้าด้วยกัน ดังนี้

CREATE TABLE IF NOT EXISTS actor_films(
    actor_id INTEGER NOT NULL REFERENCES actors(id) ON DELETE CASCADE,
    film_id INTEGER NOT NULL REFERENCES films(id) ON DELETE CASCADE,
    PRIMARY KEY (actor_id, film_id)
);
Enter fullscreen mode Exit fullscreen mode

ในมุมของ struct ใน Go เราจะสร้าง struct Actor และมี field Films เพื่อเก็บ list ของ Film ที่โยงกับ Actor แล้วเราจะใช้ gorm:"many2many:actor_films;" struct tag เพื่อบอกว่า Fields นี้จะถูกโหลดข้อมูลของ Film โดยอาศัยความสัมพันธ์ผ่านตาราง actor_films นั่นเอง

type Actor struct {
    ID        uint      `json:"id"`
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    Films     []Film    `json:"films" gorm:"many2many:actor_films;"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}
Enter fullscreen mode Exit fullscreen mode

ในตัวอย่างนี้เราก็มีอีกคู่ที่เป็น Many to Many นั่นคือ films กับ categories เราก็อาศัย table เพื่อโยงความสัมพันธ์เช่นกันชื่อ film_catagories

CREATE TABLE IF NOT EXISTS film_categories(
    film_id INTEGER NOT NULL REFERENCES films(id) ON DELETE CASCADE,
    category_id INTEGER NOT NULL REFERENCES categories(id) ON DELETE CASCADE,
    PRIMARY KEY (film_id, category_id)
);
Enter fullscreen mode Exit fullscreen mode

แล้วใน Film struct เราก็กำหนด gorm struct tag แบบนี้

type Film struct {
    ID         uint       `json:"id"`
    Title      string     `json:"title"`
    LanguageID uint       `json:"-"`
    Language   Language   `json:"language"`
    Categories []Category `json:"categories" gorm:"many2many:film_categories;"`
    CreatedAt  time.Time  `json:"created_at"`
    UpdatedAt  time.Time  `json:"updated_at"`
}
Enter fullscreen mode Exit fullscreen mode

สุดท้ายเมื่อเรา map field โดยอาศัย gorm struct tag แล้วเราสามารถใช้ method Preload เพื่อสั่งใช้ query association field อย่าง Films และ Categories หรือ Language ของ Film ก็ได้ เช่น

var dest []Actor
    db.Preload("Films.Language").Preload("Films.Categories").Find(&dest)
    json.NewEncoder(os.Stdout).Encode(&dest)
Enter fullscreen mode Exit fullscreen mode

โค้ด table ทั้งหมดในตัวอย่าง

BEGIN;

CREATE TABLE IF NOT EXISTS actors(
    id SERIAL NOT NULL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL,
    first_name CHARACTER VARYING,
    last_name CHARACTER VARYING
);

CREATE TABLE IF NOT EXISTS languages(
    id SERIAL NOT NULL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL,
    name CHARACTER VARYING
);

CREATE TABLE IF NOT EXISTS films(
    id SERIAL NOT NULL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL,
    title CHARACTER VARYING,
    language_id INTEGER NOT NULL REFERENCES languages(id)
);

CREATE TABLE IF NOT EXISTS categories(
    id SERIAL NOT NULL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL,
    name CHARACTER VARYING
);

CREATE TABLE IF NOT EXISTS film_categories(
    film_id INTEGER NOT NULL REFERENCES films(id) ON DELETE CASCADE,
    category_id INTEGER NOT NULL REFERENCES categories(id) ON DELETE CASCADE,
    PRIMARY KEY (film_id, category_id)
);

CREATE TABLE IF NOT EXISTS actor_films(
    actor_id INTEGER NOT NULL REFERENCES actors(id) ON DELETE CASCADE,
    film_id INTEGER NOT NULL REFERENCES films(id) ON DELETE CASCADE,
    PRIMARY KEY (actor_id, film_id)
);

COMMIT;
Enter fullscreen mode Exit fullscreen mode

โค้ด Go ที่ใช้ GORM ช่วย map field

package main

import (
    "encoding/json"
    "log"
    "os"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres"
)

type Actor struct {
    ID        uint      `json:"id"`
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    Films     []Film    `json:"films" gorm:"many2many:actor_films;"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

type Film struct {
    ID         uint       `json:"id"`
    Title      string     `json:"title"`
    LanguageID uint       `json:"-"`
    Language   Language   `json:"language"`
    Categories []Category `json:"categories" gorm:"many2many:film_categories;"`
    CreatedAt  time.Time  `json:"created_at"`
    UpdatedAt  time.Time  `json:"updated_at"`
}

type Language struct {
    ID        uint      `json:"id"`
    Name      string    `json:"name"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

type Category struct {
    ID        uint      `json:"id"`
    Name      string    `json:"name"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

func initialData(db *gorm.DB) {
    tx := db.Begin()
    defer tx.Commit()

    lang := Language{
        Name: "English",
    }
    tx.Create(&lang)
    cate := Category{
        Name: "Documentary",
    }
    tx.Create(&cate)
    film := Film{
        Title:      "Young Language",
        Language:   lang,
        Categories: []Category{cate},
    }
    tx.Create(&film)
    actor := Actor{
        FirstName: "Ed",
        LastName:  "Chase",
        Films:     []Film{film},
    }
    tx.Create(&actor)
}

func main() {
    connURL := os.Getenv("POSTGRESQL_URL")
    log.Println(connURL)
    db, err := gorm.Open("postgres", connURL)
    if err != nil {
        panic("failed to connect database")
    }
    defer db.Close()

    initialData(db)

    var dest []Actor
    db.Preload("Films.Language").Preload("Films.Categories").Find(&dest)
    json.NewEncoder(os.Stdout).Encode(&dest)
}
Enter fullscreen mode Exit fullscreen mode

หรือจะ clone ไปลองเล่นได้ทีนี่ https://github.com/iporsut/example-gorm-many-to-many

Top comments (0)