SQLパッケージ@Go¶
はじめに¶
本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。
01. gorm¶
gormとは¶
Go製のORMである。
その他のORMについては、以下のリポジトリが参考になる。
執筆時点 (2022/01/31) では、gormとbeegoが接戦している。
DBとの接続¶
▼ MySQLの場合¶
package db
func NewDB() (*gorm.DB, error) {
// 接続情報。sprintf関数を使用すると、可読性が高い。
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_DATABASE"),
)
// DBに接続します。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return nil, err
}
return db, nil
}
func Close(db *gorm.DB) error {
sqlDb, err := db.DB()
if err != nil {
return err
}
// DBとの接続を切断します。
err = sqlDb.Close()
if err != nil {
return err
}
return nil
}
gormモデル¶
▼ gormモデル埋め込み¶
構造体にgormモデルを埋め込むと、IDやタイムスタンプレコードをフィールドとして追加する。
構造体をマッピングしたテーブルに、対応するカラム (id
、created_at
、updated_at
、deleted_at
) を持つレコードを作成する。
package model
type User struct {
// gormモデルを埋め込む
gorm.Model
Name string
}
// 以下と同じ
type User struct {
ID uint `gorm:"primaryKey"`
Name string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeleteAt `gorm:"index"`
}
▼ データベースへのマッピング¶
Gormは、構造体をデータベースに自動的にマッピングする。
明示的に設定しない限り、スネークケースに変換して自動的にマッピングする。
package model
// usersテーブルにマッピング
type User struct {
// テーブルの主キーにマッピング
ID uint
// nameカラムにマッピング
Name string
// created_atカラムにマッピング
CreatedAt time.Time
// updated_atカラムにマッピング
UpdatedAt time.Time
// deleted_atカラムにマッピング
DeletedAt gorm.DeleteAt
}
▼ プライマリーキー¶
『ID』という名前のフィールドを認識して、これをプライマリーキーとしてデータをマッピングする。
もし、他の名前のフィールドをIDとして使用したい場合は、gorm:"primaryKey"
タグをつける。
package model
type User struct {
ID string // プライマリーキーとして使用される。
Name string
}
package model
type User struct {
UserID string `gorm:"primaryKey"` // プライマリーキーとして使用される。
Name string
}
▼ SoftDelete¶
構造体が、gorm.DeleteAt
をデータ型とするフィールドを持っていると、その構造体を使用したDELETE
処理では論理削除が実行される。
gormモデルを埋め込むことによりこのフィールドを持たせるか、または自前定義することにより、SoftDeleteを有効化できる。
package model
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
user := User{Id: 111}
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
db.Where("age = ?", 20).Delete(&User{})
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
db.Where("age = 20").Find(&user)
DBマイグレーション¶
▼ TableName
関数¶
デフォルトではgormモデルの名前をスネークケースに変更し、加えて複数形とした名前のテーブルを作成する。
TableName
関数により、ユーザー定義のテーブル名をつけられる。
package model
// テーブル名はデフォルトでは『users』になる。
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
// テーブル名を『foo』になる。
func (User) TableName() string {
return "foo"
}
01-02. トランザクション¶
トランザクションの流れ¶
- トランザクションを開始する
- 複数のCRUD処理を実行する
- コミットする
- 1-3の処理が途中で失敗した場合にロールバックする
package db
func CreateFoo(db *gorm.DB) error {
// トランザクションを開始する
tx := db.Begin()
// ロールバックは遅延実行する
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
// CREATE処理を実行する
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback()
return err
}
// CREATE処理を実行する。
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
tx.Rollback()
return err
}
// コミットする
return tx.Commit().Error
}
CREATE処理¶
▼ CREATE処理とは¶
gormモデルのフィールドに設定された値を元に、レコードを作成する。
作成したレコードのプライマリーキーを、構造体から取得できる。
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user)
user.ID
result.Error
result.RowsAffected
READ処理¶
▼ READ理とは¶
gormモデルに対応するレコードを読み出す。
▼ 全レコード取得¶
user := User{}
// SELECT * FROM users;
result := db.Find(&users)
result.RowsAffected
result.Error
▼ 単一/複数レコード取得¶
gormモデルとプライマリーキーを指定して、プライマリーキーのモデルに紐付けられたレコードを取得する。
user := User{}
// SELECT * FROM users WHERE id = 10;
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id IN (1,2,3);
db.Find(&users, []int{1,2,3})
UPDATE処理¶
▼ UPDATEとは¶
gormモデルのフィールドに設定された値を元に、レコードを変更する。
▼ 単一レコード更新 (暗黙的)¶
フィールドとは無関係に、渡された値を元にUPDATE分を実行する。
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
user := User{Id:111}
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
db.Model(&user).Where("active = ?", true).Update("name", "hello")
▼ 複数レコード更新 (暗黙的)¶
gormモデルのフィールドを暗黙的に指定して、複数のレコード値を更新する。
または、フィールドとは無関係に、mapを元にUPDATE文を実行する。
gormモデルを使用した場合、フィールド値がゼロ値であると、これに紐付けられたレコード値の更新はスキップされてしまう。
user := User{Id:111}
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: "false"})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": "false"})
▼ 複数レコード更新 (明示的)¶
gormモデルのフィールドを明示的に指定して、複数のレコード値を更新する。
フィールド値がゼロ値であっても、スキップされない。
user := User{Id:111}
// UPDATE users SET name='new_name', age=0 WHERE id=111;
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
▼ 全レコード更新¶
gormモデルのフィールドを暗黙的に全て指定して、全てのレコード値を強制的に更新する。
user := User{Id:111}
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
db.Save(&user)
01-04. クエリのオプション¶
WithContext¶
▼ WithContextとは¶
gormクエリにコンテキストを設定する。
// タイムアウト時間を設定し、コンテキストを作成する
ctx, cancel := context.WithTimeout(
context.Background(),
5 * time.Second,
)
// タイムアウト時間経過後に処理を中断する
defer cancel()
db.WithContext(ctx).Find(&users)
Exec¶
▼ Execとは¶
SQLステートメントをそのまま実行する。
db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
db.Exec(fmt.Sprintf("SET SESSION max_execution_time=%d;", 10))
Hook¶
▼ Hookとは¶
CRUDの関数の前後に設定した独自処理を実行できるようにする。
▼ 特定のCRUD関数の前後¶
package db
func NewDb() {
...
// Createの前
db.Callback().Create().Before("gorm:before_create").Register("<フック名>", "<CRUD関数名>")
...
}
▼ 全てのCRUD関数の前後¶
package db
func (user *User) BeforeSave(tx *gorm.DB) (err error) {
user.LastUpdated = time.Now()
return nil
}
package db
func (user *User) AfterSave(tx *gorm.DB) (err error) {
log.Println("User successfully saved:", user.ID)
return nil
}
Statement¶
▼ Statementとは¶
gormクエリに関する情報を持つ。
package db
type DB struct {
*Config
Error error
RowsAffected int64
Statement *Statement
clone int
}
type Statement struct {
*DB
TableExpr *clause.Expr
Table string
Model interface{}
Unscoped bool
Dest interface{}
ReflectValue reflect.Value
Clauses map[string]clause.Clause
BuildClauses []string
Distinct bool
Selects []string
Omits []string
Joins []join
Preloads map[string][]interface{}
Settings sync.Map
ConnPool ConnPool
Schema *schema.Schema
// SQLステートメントごとのコンテキスト
// SQLにタイムアウト値やキャンセル関数を設定できる
Context context.Context
RaiseErrorOnNotFound bool
SkipHooks bool
// SQLステートメント
SQL strings.Builder
Vars []interface{}
CurDestIndex int
attrs []interface{}
assigns []interface{}
scopes []func(*DB) *DB
}