GormGenのFieldRelateを使用して テーブルの関連を表した構造体を生成する
はじめに
レスキューナウでは、バックエンドの開発に主にGo言語を使用していて、データベースとのやり取りにはGORMを使用しています。
GormGenを使用して、接続しているデータベースからGORMで使用する構造体を自動生成することで、開発効率を向上させていました。ですが、テーブル同士の関連を生成することができなかったため、手動で構造体を編集する必要がありました。
GormGenの gen.FieldRelate
を使用することで手動で関連を定義する方法を調べました。
GORMについてはこちらの使い方を御覧ください
GormGenとは
GormGen
は、Go言語のORMライブラリのGORM
のプロジェクトの一つで
既存のデータベーステーブルをGo言語の構造体にマッピングしたい場合に使用します。生成するコマンドを実行すると、GORMがデータベースのスキーマを解析し、各テーブルに対応するGORMで使用できるGo言語の構造体を自動的に生成します。
GormGenは生成される構造体のフィールド名やタグのカスタマイズも可能です。生成されたコードは、指定されたディレクトリに保存されます。
GormGenの使い方
インストール
パッケージはこちら
使用するテーブル
サンプルで使用するテーブル
users
playlists
songs
のテーブルがあり
1対多の関係になっています。
CREATE TABLE SQL
CREATE TABLE IF NOT EXISTS `users` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` TEXT NULL,
`birthday` VARCHAR(45) NULL,
`address` TEXT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NULL,
`deleted_at` DATETIME NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `playlists` (
`id` BIGINT NOT NULL,
`user_id` BIGINT UNSIGNED NOT NULL,
`name` VARCHAR(45) NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NULL,
`deleted_at` DATETIME NULL,
PRIMARY KEY (`id`),
INDEX `fk_playlists_user1_idx` (`user_id` ASC),
CONSTRAINT `fk_playlists_user1`
FOREIGN KEY (`user_id`)
REFERENCES `user` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `songs` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`playlists_id` BIGINT NOT NULL,
`title` TEXT NULL,
`track` TEXT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NULL,
`deleted_at` DATETIME NULL,
PRIMARY KEY (`id`),
INDEX `fk_songs_playlists1_idx` (`playlists_id` ASC),
CONSTRAINT `fk_songs_playlists1`
FOREIGN KEY (`playlists_id`)
REFERENCES `playlists` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
簡単な使い方
DBに接続してすべてのテーブルの構造体を出力します
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gorm"
)
func main() {
g := gen.NewGenerator(gen.Config{
OutPath: "./gen/query",
Mode: gen.WithoutContext
})
gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
g.UseDB(gormdb)
g.ApplyBasic(g.GenerateAllTable()...)
g.Execute()
}
Gormで接続したDBを使用して、g.GenerateAllTable()
ですべてのテーブルを対象として
実行すると 、指定した./gen/query
に テーブルの構造体が出力されます
出力結果
生成された構造体
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameUser = "user"
type User struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
Name string `gorm:"column:name" json:"name"`
Birthday string `gorm:"column:birthday" json:"birthday"`
Address string `gorm:"column:address" json:"address"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
}
// TableName User's table name
func (*User) TableName() string {
return TableNameUser
}
package model
import (
"time"
"gorm.io/gorm"
)
const TableNamePlaylists = "playlists"
type Playlists struct {
ID int64 `gorm:"column:id;primaryKey" json:"id"`
UserID int64 `gorm:"column:user_id;not null" json:"user_id"`
Name string `gorm:"column:name" json:"name"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
}
// TableName Playlists's table name
func (*Playlists) TableName() string {
return TableNamePlaylists
}
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameSongs = "songs"
type Songs struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
PlaylistsID int64 `gorm:"column:playlists_id;not null" json:"playlists_id"`
Title string `gorm:"column:title" json:"title"`
Track string `gorm:"column:track" json:"track"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
}
// TableName Songs's table name
func (*Songs) TableName() string {
return TableNameSongs
}
すべてテーブルを指定して構造体の出力を実行しましたが、
テーブル間に関連がある場合、それぞれのテーブルの構造体を生成しただけでは、その関連性を表現することができません。
GormGen
を使用してデータベースのテーブルからGo言語の構造体を自動生成する場合、テーブル間の1対多の関係を含めた構造体を生成するためには、それぞれのテーブルとその関連を指定する必要があります。
テーブルの関連を持った構造体生成
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gorm"
)
func main() {
g := gen.NewGenerator(gen.Config{
OutPath: "./gen/query",
Mode: gen.WithoutContext
})
gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
g.UseDB(gormdb)
var fieldOpts []gen.FieldOpt
allModel := g.GenerateAllTable(fieldOpts...)
songs := g.GenerateModel("songs")
playlists := g.GenerateModel("playlists", append(
fieldOpts, gen.FieldRelate(field.HasMany, "Songs", songs,
&field.RelateConfig{
GORMTag: "foreignKey:PlaylistID",
}))...,
)
users := g.GenerateModel("user", append(
fieldOpts, gen.FieldRelate(field.HasMany, "Playlists", playlists,
&field.RelateConfig{
RelateSlice: true,
GORMTag: "foreignKey:UserID",
}))...,
)
g.ApplyBasic(users, playlists, songs)
g.ApplyBasic(allModel...)
g.Execute()
}
関連するフィールドに対してgen.FieldRelate
を使用します。
対象のフィールドに外部キーを追加し、テーブル間の関連を明示的に表現することができます。
このコードでは
songs := g.GenerateModel("songs")
で関連するテーブルを定義して
gen.FieldRelate(field.HasMany, "Songs", songs,&field.RelateConfig{
GORMTag: "foreignKey:PlaylistID",
}))...,
で関連付けを明示的に行っています、field.HasMany
で 1対多の関係
"Songs"
の箇所で構造体のfield名を定義します
GORMTag: "foreignKey:PlaylistID"
で fieldに対してforeignKeyの指定を行うことで
テーブル間の関連をもった構造体を生成することができます。
HasMany
以外にもHasOne
BelongsTo
Many2Many
を指定できます
const (
HasOne RelationshipType = RelationshipType(schema.HasOne) // HasOneRel has one relationship
HasMany RelationshipType = RelationshipType(schema.HasMany) // HasManyRel has many relationships
BelongsTo RelationshipType = RelationshipType(schema.BelongsTo) // BelongsToRel belongs to relationship
Many2Many RelationshipType = RelationshipType(schema.Many2Many) // Many2ManyRel many to many relationship
)
生成された構造体
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameUser = "user"
type User struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
Name string `gorm:"column:name" json:"name"`
Birthday string `gorm:"column:birthday" json:"birthday"`
Address string `gorm:"column:address" json:"address"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
Playlists []Playlists `gorm:"foreignKey:UserID" json:"playlists"`
}
// TableName User's table name
func (*User) TableName() string {
return TableNameUser
}
package model
import (
"time"
"gorm.io/gorm"
)
const TableNamePlaylists = "playlists"
type Playlists struct {
ID int64 `gorm:"column:id;primaryKey" json:"id"`
UserID int64 `gorm:"column:user_id;not null" json:"user_id"`
Name string `gorm:"column:name" json:"name"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
Songs []Songs `gorm:"foreignKey:PlaylistID" json:"songs"`
}
// TableName Playlists's table name
func (*Playlists) TableName() string {
return TableNamePlaylists
}
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameSongs = "songs"
type Songs struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
PlaylistsID int64 `gorm:"column:playlists_id;not null" json:"playlists_id"`
Title string `gorm:"column:title" json:"title"`
Track string `gorm:"column:track" json:"track"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
}
// TableName Songs's table name
func (*Songs) TableName() string {
return TableNameSongs
}
おわり
gen.FieldRelate
を使用することで、テーブル間の関連を明示的に定義することができます。
generator自体に設定を記載していくことで、手動で構造体を編集する必要がなくなり、ファイルをメンテする手間が減らせます。
Discussion