Go言語で簡単にDBアクセスを実現!GORMを使ってORMを体験しよう-Create編(1)
はじめに
前回は GORM の導入方法と基本的な機能を紹介しました。
gorm.Open
関数を呼び出した結果、取得される gorm.DB
インスタンス ( インスタンスと呼んでいいのか Go 言語の思想がわからませんが ) のメソッドを使って様々な操作を行うことができます。
今回は Create
(登録処理)に関するさまざまな機能を紹介します。
Create ( 登録 )
gorm.DB#Create()
を使います。
サンプルコードを以下に記載します。
package main
import (
"fmt"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age uint
Biarthday time.Time
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
user := User{Name: "Jinzhu", Age: 18, Biarthday: time.Now()}
result := db.Create(&user)
fmt.Printf("%+v\n", user.ID) // Value of user.ID is 1
fmt.Printf("%+v\n", result.Error) // Value of result.Error is nil
fmt.Printf("%+v\n", result.RowsAffected) // Value of result.RowsAffected is 1
}
gorm.DB#Create()
を実行した結果は「戻り値」と「引数で指定した構造体(のポインタ)」から取得できます。
Create()
メソッドには構造体のスライスを指定し同時に複数のデータを登録できます。
package main
import (
"fmt"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age uint
Biarthday time.Time
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
users := []*User{
&User{Name: "Jinzhu", Age: 18, Biarthday: time.Now()},
&User{Name: "Jackson", Age: 20, Biarthday: time.Now()},
}
result := db.Create(&users)
fmt.Printf("%+v\n", result.Error) // Value of result.Error is nil
fmt.Printf("%+v\n", result.RowsAffected) // Value of result.RowsAffected is 2
for _, user := range users {
fmt.Printf("%+v\n", user.ID) // First user's ID is 1, second user's ID is 2
}
}
一部のフィールドだけを使う
構造体に設定されているフィールドのうち、一部のフィールドを選択して登録に利用できます。
User
構造体には Name
/ Age
/ Birthday
の 3 フィールドに値をセットしましたが、登録には Name
, Birthday
だけを使用します。
package main
import (
"fmt"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age uint
Biarthday time.Time
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
time, _ := time.Parse("2006-01-02", "1990-01-01")
user := User{Name: "Jinzhu", Age: 18, Biarthday: time}
db.Select("Name", "Biarthday").Create(&user)
fmt.Printf("%+v\n", user.ID) // 1
fmt.Printf("%+v\n", user.Name) // Jinzhu
fmt.Printf("%+v\n", user.Age) // 18
fmt.Printf("%+v\n", user.Biarthday) // 1990-01-01 00:00:00 +0000 UTC
// データをロードし直す
db.Find(&user, user.ID)
fmt.Printf("%+v\n", user.Name) // Jinzhu
fmt.Printf("%+v\n", user.Age) // 18
fmt.Printf("%+v\n", user.Biarthday) // 1990-01-01 00:00:00 +0000 UTC
}
逆に、無視するフィールドを指定することもできます。
time, _ := time.Parse("2006-01-02", "1990-01-01")
user := User{Name: "Jinzhu", Age: 18, Biarthday: time}
db.Omit("Age").Create(&user)
fmt.Printf("%+v\n", user.ID) // 1
fmt.Printf("%+v\n", user.Name) // Jinzhu
fmt.Printf("%+v\n", user.Age) // 18
fmt.Printf("%+v\n", user.Biarthday) // 1990-01-01 00:00:00 +0000 UTC
// データをロードし直す
db.Find(&user, user.ID)
fmt.Printf("%+v\n", user.Name) // Jinzhu
fmt.Printf("%+v\n", user.Age) // 18
fmt.Printf("%+v\n", user.Biarthday) // 1990-01-01 00:00:00 +0000 UTC
バッチインサート
先程も紹介したとおり、大量レコードを効率的に登録するためにCreate
メソッドに対してスライスを渡すことができます。
GORM は単一の insert 文に複数の値を詰め込んだ SQL ( バッチインサート ) を生成してくれます。
データベースの機能で、トランザクションは単一となるため、すべての insert に成功するか、失敗するかのいずれかになります。
var users = []User{{Name: "taro"}, {Name: "jiro"}, {Name: "saburo"}}
db.Create(&users)
for _, user := range users {
fmt.Println(user.ID) // 1, 2, 3
}
バッチサイズを指定して、1回の SQL で登録されるレコード数を制御することも可能です。
( 登録件数が多すぎる場合、長時間のトランザクション発生によりデータベースの負荷が懸念されるケースに対処できます。
)
users := make([]*User, 100)
for i := 0; i < 100; i++ {
name := fmt.Sprintf("user%d", i)
users[i] = &User{Name: name, Age: 18, Biarthday: time.Now()}
}
db.CreateInBatches(users, 10) // it will create 10 records per SQL
更には、デフォルトのバッチサイズを指定することもできます。Create
メソッド呼び出し時には小音設定値にしたがってマルチインサートを行うようになります。
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
CreateBatchSize: 1000,
})
db := db.Session(&gorm.Session{CreateBatchSize: 1000})
users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}
db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)
Create フック
登録や更新の前後をイベントのフックタイミングにして、処理を実行させることができます。
以下のようなメソッドを定義することで、処理を発火できます。
BeforeSave
BeforeCreate
AfterSave
AfterCreate
各フックメソッド実行のタイミングをサンプルコードで見ていきます。
ここで db.Save()
というメソッドが登場していますが、これは引数に渡された構造体の ID
(Primary Key)が nil 値の場合には INSERT を、 非 nil 値の場合には UPDATE を実行するメソッドです。
package main
import (
"fmt"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age uint
Biarthday time.Time
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.Name += "+BC"
fmt.Println("BeforeCreate : " + u.Name)
return
}
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
u.Name += "+BS"
fmt.Println("BeforeSave: " + u.Name)
return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
u.Name += "+AC"
fmt.Println("AfterCreate: " + u.Name)
return
}
func (u *User) AfterSave(tx *gorm.DB) (err error) {
u.Name += "+AS"
fmt.Println("AfterSave: " + u.Name)
return
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
birthday, _ := time.Parse("2006-01-02", "2006-01-02")
u1 := User{Name: "genzouw", Age: 15, Biarthday: birthday}
db.Save(&u1)
var u2 User
db.First(&u2, u1.ID)
fmt.Printf("ID=%d, Name=%s\n", u2.ID, u2.Name)
u2.Name = "genzouw2"
db.Save(&u2)
var u3 User
db.First(&u3, u1.ID)
fmt.Printf("ID=%d, Name=%s\n", u3.ID, u3.Name)
}
実行結果は以下のようになります。
BeforeSave: genzouw+BS
BeforeCreate : genzouw+BS+BC
AfterCreate: genzouw+BS+BC+AC
AfterSave: genzouw+BS+BC+AC+AS
ID=1, Name=genzouw+BS+BC
BeforeSave: genzouw2+BS
AfterSave: genzouw2+BS+AS
ID=1, Name=genzouw2+BS
実行結果からわかることをまとめると以下のようになります。
メソッド | 実行順序 | insert 時に実行? | update 時に実行? | DB に反映? |
---|---|---|---|---|
BeforeSave | 1 | o | o | o |
BeforeCreate | 2 | o | o | |
AfterCreate | 3 | o | ||
AfterSave | 4 | o | o |
insert、update 関わらずデータベースへの保存前に処理を行いたい場合は、 BeforeSave
で実装するのが良いでしょう。
フック機能は便利ですが、どうしても公開の登録・更新・削除処理でだけ無効にしたい ( ex: マイグレーションしたい ) ときには、一時的に無効にできます。
以下の例では、 BeforeSave
/ BeforeCreate
/ AfterCreate
/ AfterSave
のいずれのフックも実行されません。
birthday, _ := time.Parse("2006-01-02", "2006-01-02")
u1 := User{Name: "genzouw", Age: 15, Biarthday: birthday}
db.Session(&gorm.Session{SkipHooks: true}).Save(&u1)
構造体ではなくマップを使う
構造体ではなくマップを使って登録を行うことができます。
- 単一レコードの登録には
&map[string]inferface{}
- 複数レコードの登録には
&[]map[string]inferface{}
(マップのスライス)
を Create
メソッドの引数に指定します。
db.Model(&User{}).Create(&map[string]interface{}{
"Name": "taro", "Age": 18,
})
db.Model(&User{}).Create(&[]map[string]interface{}{
{"Name": "jiro", "Age": 20},
{"Name": "saburo", "Age": 19},
})
ひとこと
思ったよりも長くなりそうなので、次回に続きます。
ディスカッション
コメント一覧
まだ、コメントがありません