全体像:gorm.DBは「DB操作の状態+設定」の塊
GROM v2系では,gorm.DBの定義はざっくりというと次のような構造になっている.
type DB struct {
Config *Config // 設定(Logger、DryRun、SkipDefaultTransactionなど)
Error error // 直近のエラー
RowsAffected int64 // 直近のクエリで影響を受けた行数
Statement *Statement // 実行中のSQLに関する情報(テーブル、条件、値など)
// 以下、セッション状態・コールバック・フックなど
}
ポイントとして
- DBは「データベース接続のハンドル」+「直近のクエリ状態」をまとめたもの
- 内部でさらにConfigやStatementなどの構造体を持っている
主なフィールドの意味
Config *Config
ConfigはGORMの動作設定のこと
具体例
- 使用するログ(Logger)
- トランザクションのデフォルト挙動
- テーブル名の命名規則
- プリロード(関連取得)の設定
など
イメージ
type Config struct {
Logger logger.Interface
SkipDefaultTransaction bool
NamingStrategy schema.Namer
// ...
}
よってよく使われるものとして,
gorm.Open(postgres.Open(dsn), &gorm.Config{})
のなかの,&gorm.Config{}が当たる.
ちなみにこのコードは「GORMでPostgreSQLに接続するためのエンジンをつくる処理」である.
- dsn:どのDBにどのユーザーでどのパスワードで接続するかを文字列で表したもの
- Open(dsn):PostgreSQL用のドライバに,このDNSで接続してという設定オブジェクトをつくる.設定オブジェクトとは,プログラムの動作方法(ルール,挙動,オプション)をまとめて一つにしたもの
- &gorm.Config{}:GORM側の設定で,ログや命名規則などのGORMの挙動を制限する設定の入れ物で,空にするとデフォルト設定になる
- gorm.Open():引数の二つを組み合わせて,DBに接続できる*gorm.DBオブジェクトを作り,戻り値がdb *gorm.DB, err errorの形で返ってくる
Error error
直近のクエリで発生したエラーがここに入る.
result := db.Find(&items)
if result.Error != nil {
// ここでエラー処理
}
のresult.Errorは,内部的にgorm.DBのErrorフィールド.
RowsAffected int64
直近のクエリで「何行影響を受けたか」
- SELECT:取得件数
- UPDATE/DELETE:更新・削除された件数
var users []User
result := db.Where("age >= ?", 18).Find(&users)
fmt.Println(result.RowsAffected) // 取得できたレコードの件数
result := db.Model(&User{}).Where("active = ?", true).Update("age", 20)
fmt.Println(result.RowsAffected) // 更新された行数
result := db.Where("age < ?", 10).Delete(&User{})
fmt.Println(result.RowsAffected) // 削除された行数
第一引数が構造体のアドレスで,第二引数がid(Primary key)となり,idを用いてWHERE id = ?が生成される.
また,第一引数で&User{}のように型だけ教えるダミーを渡すことも可能.
result := db.Where("age < ?", 10).Delete(&User{})
Statement *Statement
今まさに実行しようとしている(または実行した)SQLの「構成情報」が詰まっている.中には,
- どのテーブルに対して
- どの条件で
- どの値を使って
- どのカラムを更新するか
- プレーズホルダとの値の対応
などが入る.
Where(), Model(), Table()などを繋げていくと,このStatementがどんどん組み立てられ,最終的にSQLに変換される.
注釈
プレーズホルダ:SQLの中で,値を直接埋め込ませずに「後から値を入れる穴」を作るための記号のこと
「接続プール」はどこにあるのか
GROMは内部で*sql.DB(Go標準のDB接続プール)を抱えているが,それはgorm.DBからさらに下の層(gorm.io/gorm@schema, gorm.io/driver/postgresなど)に隠蔽されており,直接gorm.DBのフィールドとしては見えていない(ドライバ側の構造体に入っている).
イメージとしては
gorm.DB
└── Config
└── Statement
└── (内部で)gorm.io/driver/postgres の構造体
└── *sql.DB(接続プール)
という多層構造になっている.
注釈
- プール:同じ種類の何か(オブジェクトや接続など)を複数まとめて管理し,必要なときに貸し出したり返却したりする仕組み.接続プール(コネクションプール)とは,簡単にいうとメモリのようなもので,DBとの接続は作るのにコストが高い(毎回CONNECT→DISCONECTにする)ので,あらかじめ一定の接続を作っておき,必要な時にその接続を借りて使い,使い終わったら破棄ではなくプールに返すという方式を取る.GORMの内部でdatabase/sqlの*sql.DBを使い,その中で接続プールを管理している.
- .io:TDL(トップレベルドメイン)の一種で,GORMの公式ドメインとして使われている
- ドライバ:あるソフトウェア(ここではGORMやdatabase/sql)が特定の外部システム(ここではPostgreSQLなどのDB)とやり取りするための「橋渡し役」.DBの種類ごとにプロトコル(通信するときの決まり事(ルールのセット))が違うためそれを渡す.
なぜgorm.DBが「セッションオブジェト」と呼ばれるか
GORM v2では,dbに対してメソッドチェーンするたびに「新しいセッションを返す」イメージになっている.
db.Where("name = ?", "Taro").Find(&users)
ここで
- db自体は元の「ベース」
- db.Where(...)はdbを元にした「条件付きセッション」
そのセッションの状態(条件,モデル,プレロード情報など)がStatementに入っている.
注釈
- メソッドチェーン:メソッドの戻り値に対してさらにメソッドを呼び続けて,一つの流れとして処理をつなげていく書き方
- セッション:一連のDB操作の設定や状態をまとめたもので,この一連の操作はこういう設定で実行してくれという単位
- プレロード情報:プレロードとはテーブルの中の別テーブルのデータも一緒に取ってくることで,どの関連(別テーブル)をプレロードするかという情報をSatementの中に保存する内容のこと
- Statement:今から実行する(または実行した)SQLに関する情報の塊のこと.GORMはWhere / Model / Table / Select / Order などを呼ぶごとにStatementを組み立て,最後の Find / Create / Update などのタイミングでSQLに変換してDBに投げる.
まとめ
知っておくことは以下のこと
- gorm.DBは「DB操作のための構造体」
- *gorm.DBを使うのは「同じ接続・設定・状態を共有するため」
- 内部的には:
- Config(ログ・命名規則・挙動設定)
- Error(直近のエラー)
- RowsAffected(直近の影響行数)
- Statement(現在のクエリ状態)
が入っている.
- 実際の接続プールはドライバ内部で管理されているので,意識する必要はあまりない.