Go言語 GORM 1:N (One To Many) 関係モデリングとクエリ
今回の記事ではGORMを使って一対多(one to many)の関係モデリングをする方法とクエリをする方法について見ていきましょう。
一対多の関係は、親と子、あるいは会社と社員、学校と生徒などの関係を指しますが、学校は多数の生徒を持つことができますが、生徒はただ1つの学校に属するという場合を考えていただければよいです。
ごく簡単な例として、Book(本)👉Shop(店)👉Region(地域)の関係を持ったテーブルを作っていきます。
まず、ごく簡単にモデリングをしていきましょう。
package models
type Region struct {
ID int
Name string
Shops []Shop
}
type Shop struct {
ID int
Name string
RegionID int
Books []Book
}
type Book struct {
ID int
Name string
Price float64
ShopID int
}私はMySQLを使います。 データベースにbook_storeというデータベースを作成し、次のコードを実行してテーブルを作成していきます。
package main
import (
"github.com/sanghee911/bookstore/backend/src/models"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func Connect() {
var err error
DB, err = gorm.Open(mysql.New(mysql.Config{
DSN: "root:root123@tcp(dev-server:3306)/book_store?charset=utf8mb4&parseTime=True&loc=Local",
DefaultStringSize: 256,
DisableDatetimePrecision: true,
}), &gorm.Config{})
if err != nil {
panic("Could not connect to the database!")
}
}
func AutoMigrate() {
err := DB.AutoMigrate(
&models.Region{}, &models.Shop{}, &models.Book{},
)
if err != nil {
panic(err)
}
}
func main() {
Connect()
AutoMigrate()
}データベースに次のようなテーブルが作成されました。一目で3つのテーブルがどのような関係を持っているかわかります。

次はデータを保存していきましょう。
package main
import (
"github.com/sanghee911/bookstore/backend/src/models"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func Connect() {
var err error
DB, err = gorm.Open(mysql.New(mysql.Config{
DSN: "root:root123@tcp(dev-server:3306)/book_store?charset=utf8mb4&parseTime=True&loc=Local",
DefaultStringSize: 256,
DisableDatetimePrecision: true,
}), &gorm.Config{})
if err != nil {
panic("Could not connect to the database!")
}
}
func AutoMigrate() {
err := DB.AutoMigrate(
&models.Region{}, &models.Shop{}, &models.Book{},
)
if err != nil {
panic(err)
}
}
func main() {
Connect()
AutoMigrate()
}データはそれぞれのテーブルに保存され、外部キー(foreign key)として使われているregion_idとshop_idが自動で保存されたことがわかります。



SQLコマンドでそれぞれのテーブルを連結してデータを出力するには、JOINコマンドを使えばよいです。
select r.name as regionName, s.name as shopName, b.name as bookName from regions r join shops s on r.id = s.region_id join books b on s.id = b.shop_id;すると、次のようなテーブルが出力されます。
次は同じテーブルをGORMが提供するPreloadメソッドを使って、テーブルをジョインして出力していきましょう。
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/sanghee911/bookstore/backend/src/models"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)
func main() {
dsn := "root:root123@tcp(dev-server:3306)/book_store?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: dsn,
}), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
var region models.Region
db.Preload("Shops.Books").First(®ion)
data, _ := json.Marshal(region)
var prettyJSON bytes.Buffer
_ = json.Indent(&prettyJSON, data, "", "\t")
fmt.Println(prettyJSON.String())
}結果値はJSON形式で次のように出力されました。
{
"ID": 1,
"Name": "東京",
"Shops": [
{
"ID": 1,
"Name": "東京1号店",
"RegionID": 1,
"Books": [
{
"ID": 1,
"Name": "Python講座 第1巻",
"Price": 20000,
"ShopID": 1
},
{
"ID": 2,
"Name": "Python講座 第2巻",
"Price": 20000,
"ShopID": 1
},
{
"ID": 3,
"Name": "Python講座 第3巻",
"Price": 20000,
"ShopID": 1
}
]
},
{
"ID": 2,
"Name": "東京2号店",
"RegionID": 1,
"Books": [
{
"ID": 4,
"Name": "Go講座 第1巻",
"Price": 20000,
"ShopID": 2
},
{
"ID": 5,
"Name": "Go講座 第2巻",
"Price": 20000,
"ShopID": 2
},
{
"ID": 6,
"Name": "Go講座 第3巻",
"Price": 20000,
"ShopID": 2
}
]
}
]
}テーブルのJOINはうまくいきましたが、上でSQLで実行したのと同じ形式のデータが出力されたわけではありません。SQLコマンドを実行したのと同じように、SELECTコマンドを使ってみましょう。
package main
import (
"bytes"
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)
type Result struct {
RegionName string
ShopName string
BookName string
}
func main() {
dsn := "root:root123@tcp(dev-server:3306)/book_store?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: dsn,
}), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
var results []Result
db.Table("regions").
Select("regions.name as RegionName, shops.name as ShopName, books.name as BookName").
Joins("join shops on shops.region_id = regions.id join books on books.shop_id = shops.id").
Scan(&results)
data, _ := json.Marshal(results)
var prettyJSON bytes.Buffer
_ = json.Indent(&prettyJSON, data, "", "\t")
fmt.Println(prettyJSON.String())
}SQLで実行したデータと同じデータが出力されました。
[
{
"RegionName": "東京",
"ShopName": "東京1号店",
"BookName": "Python講座 第1巻"
},
{
"RegionName": "東京",
"ShopName": "東京1号店",
"BookName": "Python講座 第2巻"
},
{
"RegionName": "東京",
"ShopName": "東京1号店",
"BookName": "Python講座 第3巻"
},
{
"RegionName": "東京",
"ShopName": "東京2号店",
"BookName": "Go講座 第1巻"
},
{
"RegionName": "東京",
"ShopName": "東京2号店",
"BookName": "Go講座 第2巻"
},
{
"RegionName": "東京",
"ShopName": "東京2号店",
"BookName": "Go講座 第3巻"
}
]今後の記事で、1:1関係とN:N関係のモデリングとクエリ方法も整理します。