Go言語で簡単にDBアクセスを実現!GORMを使ってORMを体験しよう-Create編(2)
はじめに
以下の 2 つのエントリで Go 言語で利用可能な O/R マッパーである GORM の概要、導入方法、基本的な使い方とデータの登録方法について学びました。
- Go 言語で簡単に DB アクセスを実現!GORM を使って ORM を体験しよう-導入編 | Go 言語用ポストイット
- Go 言語で簡単に DB アクセスを実現!GORM を使って ORM を体験しよう-Create 編 ( 1 )
今回は前回のデータ登録 Create
で説明しきれなかった登録機能とアソシエーション(テーブル同士の関連)の設定されているデータの登録方法を学びます。
また、 default
キーワードを使ってカラムを作成したときにハマるケースについても説明します。
「Belongs To」アソシエーションと関連モデルの登録方法
belongs to
アソシエーションは1対1のモデル関係を表現する際に使用されます。
特に、一方のモデルがもう一方に「属する」 ( 依存している ) 関係を表現します。
例をあげます。
作成中のアプリケーションに users と companies という 2 つのモデルがあるとします。
users モデルは必ず companies テーブルのレコードに依存しているとします。
このような状況で users テーブルと companies テーブルにデータを登録する処理を、 GORM のコードで記述してみます。
( データベース環境の用意を省略するために、今回は SQLite を使用しています )
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// companies テーブルと対になる
type Company struct {
gorm.Model
Name string
}
// users テーブルと対になる
type User struct {
gorm.Model
Name string
CompanyID uint
Company Company
}
func main() {
// DBに接続
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// DBマイグレーション(テーブルを作成)
db.AutoMigrate(&Company{})
db.AutoMigrate(&User{})
// (1) users レコードと companies レコードを同時に作成する
db.Create(&User{
Name: "taro",
Company: Company{Name: "ninten-done"},
})
// (2) users レコードと companies レコードを別々に作成する
saga := Company{
Name: "saga",
}
db.Create(&saga)
// 事前に作成した companies レコードを使って users レコードを作成する
db.Create(&[]User{
// モデルを指定して作成
User{
Name: "jiro",
Company: saga,
},
User{
Name: "saburo",
Company: saga,
},
// モデルのIDを指定して作成
User{
Name: "shiro",
CompanyID: saga.ID,
},
})
}
User
オブジェクト(構造体だがドキュメントには Object と書かれているのでこの文言を使用します) は所属するモデルをフィールドに保つ必要があります。
そのため Company
を設定しますが、加えて CompanyID
も必要となります。CompanyID
フィールドは暗黙的に外部キーとして使用されます。
登録された users
テーブルと companies
テーブルをまとめて取得したい場合は、クエリ実行の際に DB#Preload()
あるいは DB#Joins()
メソッドを呼び出します。
このあたりのメソッドもよくある O/R マッパー同様の機能となります。
DB#Preload()
: アソシエーションが設定されているテーブルを複数回の SQL 発行で取得します(引数に指定されたモデル数分)DB#Joins()
: アソシエーションが設定されているテーブルを1回の SQL 発行で取得する
// すべてのユーザー情報を取得
var users []User
db.Preload("Company").Find(&users)
for _, user := range users {
println(user.Name, user.Company.Name)
}
User オブジェクトに設定されているアソシエーションである Company を無視させたい場合には、 Omit()
メソッドにモデル名を指定しスキップさせることもできます。
// Company が設定されているが company_id は null となる
db.Omit("Company").Create(&User{
Name: "ichiro",
Company: Company{
Name: "SHIKAKU",
},
})
}
デフォルト値 ( default
)
構造体フィールドのタグに default
キーワードを指定して、フィールドの値が "ゼロ値" だった時の値を強制できます。
( ちなみに構造体の名前はパッケージ外から参照しない場合は小文字から初めて問題ありません )
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type user struct {
gorm.Model
Name string `gorm:"default:'名無しさん'"`
Age uint `gorm:"default:999"`
}
func main() {
// DBに接続
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&user{})
db.Create(&user{Name: "Taro", Age: 20})
db.Create(&user{Age: 30})
db.Create(&user{Name: "Jiro"})
db.Create(&user{Name: "", Age: 0})
var users []user
db.Find(&users)
for _, user := range users {
println(user.Name, user.Age) // Taro 20, 名無しさん 30, Jiro 999, 名無しさん 999
}
}
ゼロ値 については注意が必要です。
今回の例では、 string
のゼロ値である空文字と、 uint
のゼロ値である 0 を指定した場合に default
の設定が機能していますが、値に "" や 0 が想定されるような場合に注意しましょう。
特にハマりやすいのは bool
型のフィールドに default
タグを設定した場合です。
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Deleted bool `gorm:"default:true"`
}
func main() {
// DBに接続
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
user := User{Name: "Taro", Deleted: false}
db.Create(&user)
fmt.Printf("Name: %s, Deleted: %t", user.Name, user.Deleted) // Name: Taro, Deleted: true
}
Deleted
フィールドには false
をセットしているにも関わらず、最新の情報は true
となっています。
bool
フィールドのゼロ値が false
のため、 default の設定値が利用されています。
これを回避するために sql.NullBool
という値を利用します。
package main
import (
"database/sql"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Deleted sql.NullBool `gorm:"default:true"`
}
func main() {
// DBに接続
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
user := User{Name: "Taro", Deleted: sql.NullBool{Bool: false, Valid: true}}
db.Create(&user)
fmt.Printf("Name: %s, Deleted: %t", user.Name, user.Deleted) // Name: Taro, Deleted: {false true}
}
sql.NullBool
は構造体となっており、 Bool
フィールドに値を保持しています。
NULL の場合には Valid
フィールドに false をセットします。
(したがって、 Valid
が true の場合には Bool
フィールドの値は意味がありません )
今回は false が登録できました。
ひとこと
またまた思ったよりもボリュームがあり説明しきれませんでした。
次のエントリに続きます。
ディスカッション
コメント一覧
まだ、コメントがありません