今回は GORM で Many-to-many 関係を扱う。
複数のユーザと複数の楽曲が用意されていて、各ユーザのお気に入りの楽曲を格納できるようにする。E-R図にするとこんな感じ。
とりあえずコードはこんな感じ。
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" ) const ( dsn = "root@/association?parseTime=true&loc=Asia%2fTokyo" ) type User struct { gorm.Model Name string FavMusics []Music `gorm:"many2many:user_fav_musics;"` } type Music struct { gorm.Model Title string } func main() { db := prepare() defer db.Close() for _, id := range []uint{1, 2, 3} { var user User db.First(&user, id).Related(&user.FavMusics, "FavMusics") fmt.Println("---------------------------") fmt.Printf("%s さんのお気に入り曲\n", user.Name) for _, music := range user.FavMusics { fmt.Printf("曲名: %s\n", music.Title) } } fmt.Println("---------------------------") } func prepare() *gorm.DB { db, err := gorm.Open("mysql", dsn) if err != nil { panic("Failed to connect database") } //db.LogMode(true) db.DropTableIfExists(&User{}, &Music{}, "user_fav_musics") db.AutoMigrate(&User{}) db.AutoMigrate(&Music{}) prepareUsers(db) prepareMusics(db) prepareFavMusics(db) return db } func prepareUsers(db *gorm.DB) { var usernames = []string{"Mario", "Luigi", "Peach"} for _, username := range usernames { user := User{ Name: username, } db.Create(&user) } } func prepareMusics(db *gorm.DB) { var titles = []string{ "放課後ロマンス", "私Loveなオトメ", "ニーハイエゴイスト", "禁断無敵のダーリン", "S・M・L", } for _, title := range titles { music := Music{ Title: title, } db.Create(&music) } } func prepareFavMusics(db *gorm.DB) { // お気に入り楽曲 // {user.ID, music.ID} の組 var favorites = [][]uint{ {1, 1}, {1, 2}, {1, 4}, {2, 2}, {2, 3}, {2, 5}, {3, 2}, {3, 5}, } for _, favorite := range favorites { var user User user.ID = favorite[0] var music Music music.ID = favorite[1] db.Model(&user).Association("FavMusics").Append(&music) } }
(実行結果)
--------------------------- Mario さんのお気に入り曲 曲名: 放課後ロマンス 曲名: 私Loveなオトメ 曲名: 禁断無敵のダーリン --------------------------- Luigi さんのお気に入り曲 曲名: 私Loveなオトメ 曲名: ニーハイエゴイスト 曲名: S・M・L --------------------------- Peach さんのお気に入り曲 曲名: 私Loveなオトメ 曲名: S・M・L ---------------------------
Model の定義
type User struct { gorm.Model Name string FavMusics []Music `gorm:"many2many:user_fav_musics;"` } type Music struct { gorm.Model Title string }
モデルはユーザ(User)と楽曲(Music)のみ定義する。User 側に FavMusics というフィールドを追加し、ここにお気に入りの楽曲が格納されるようにする。また FavMusics フィールドのタグで、many-to-many 関係を使用すること、および関係テーブルの名称を上記のように宣言する。AutoMigration 使用時は GORM が関係テーブル user_fav_musics を自動的に作成してくれる。
検索
db.First(&user, id).Related(&user.FavMusics, "FavMusics")
特定ユーザに紐づくお気に入り楽曲を取得する場合は、Related()の第2引数でフィールド名を指定する。LogMode を true にすれば、上記を実行したとき以下のようなSQL文が発行されているのが分かる。
User 検索
SELECT * FROM `users` WHERE `users`.deleted_at IS NULL AND ((`users`.`id` = '3')) ORDER BY `users`.`id` ASC LIMIT 1
User のお気に入り楽曲を検索
SELECT `musics`.* FROM `musics` INNER JOIN `user_fav_musics` ON `user_fav_musics`.`music_id` = `musics`.`id` WHERE `musics`.deleted_at IS NULL AND ((`user_fav_musics`.`user_id` IN ('3'))) ORDER BY `musics`.`id` ASC
関係の追加
var user User user.ID = favorite[0] var music Music music.ID = favorite[1] db.Model(&user).Association("FavMusics").Append(&music)
多分 Association Mode を使う方法が一番素直なのではないかと。Association() の引数で関係を定義したフィールド名を指定し、Append()で追加する。ちなみに user, music は、両者の関連を特定するために必要なフィールド(ここではID)だけ定義されていれば十分。あらかじめすべてのフィールドをDBから問い合わせておく必要はない。
INSERT INTO `user_fav_musics` (`music_id`,`user_id`) SELECT '1','1' FROM DUAL WHERE NOT EXISTS (SELECT * FROM `user_fav_musics` WHERE `music_id` = '1' AND `user_id` = '1')
発行されるSQL文は上記の通り。同じ組み合わせがすでに存在する場合、二重に登録されることはないようだ。