GORMのソースコードを読む3 – Createその2

ちょっと大変だが、createに関する各CallbackProcessorについて中身を見ていこう。createに関するコールバックはcallback_create.goに定義されている。まずは、最初に登録されるbeginTransactionCallback から。

func beginTransactionCallback(scope *Scope) {
	scope.Begin()
}

最初に呼ばれるbeginTransactionCallbackはその名前の通り、トランザクションを開始するためのコールバック。具体的な処理はScopeのBegin関数に実装されている。

// Begin start a transaction
func (scope *Scope) Begin() *Scope {
	if db, ok := scope.SQLDB().(sqlDb); ok {
		if tx, err := db.Begin(); err == nil {
			scope.db.db = interface{}(tx).(SQLCommon)
			scope.InstanceSet("gorm:started_transaction", true)
		}
	}
	return scope
}

中では、まずscope.SQLDB()のBegin()を呼び出している。

// SQLDB return *sql.DB
func (scope *Scope) SQLDB() SQLCommon {
	return scope.db.db
}

gormのDBを指すdbと、接続先のSQLデータベースを指すdbが混在していて紛らわしいが、このSQLDB()は結果的に接続先のSQLデータベースの情報を表すdbを返す。

dbSQL, err = sql.Open(driver, source)

dbの中身は、Open関数内で標準のsqlパッケージのOpenを使って接続したDBの接続情報である。

tx, err := db.Begin()

dbのBegin()を呼び出して、トランザクションを開始している。

scope.db.db = interface{}(tx).(SQLCommon)

正常にトランザクションを開始できた場合は、そのscopeのdb.dbにトランザクション(sql.Tx)オブジェクトをセットしている。もともとsql.DB型の、DB接続情報が入っていた箇所に別のsql.Tx型のトランザクションをセットするのは一見わかりにくいような気がするのだが、なんのためにこういう構造になっているのだろう。

Beginに失敗した場合(トランザクションの開始に失敗した場合)のハンドリングが行われていない。ここでエラーが発生した場合にも、callCallbacksの方で次のCallbackが呼ばれることになるため、そこでエラーハンドリングが行われると予想されるので、後ほど注意してみてみよう。

scope.InstanceSet("gorm:started_transaction", true)

scope.db.dbにトランザクションをセットしたら、gorm:started_transactionをセットしている。

// InstanceSet set instance setting for current operation, but not for operations in callbacks, like saving associations callback
func (scope *Scope) InstanceSet(name string, value interface{}) *Scope {
	return scope.Set(name+scope.InstanceID(), value)
}

InstanceSetは、コメントによると「一連のoperationsについてではなく、current operationに関する設定」をセットする、と説明されている。

// InstanceID get InstanceID for scope
func (scope *Scope) InstanceID() string {
	if scope.instanceID == "" {
		scope.instanceID = fmt.Sprintf("%v%v", &scope, &scope.db)
	}
	return scope.instanceID
}

InstanceID()はこのscopeを表す文字列を返す。実際の中身はscope自体のインスタンスとそのdb(このタイミングでセットされているのはトランザクション情報)から文字列を生成している。

// Set set value by name
func (scope *Scope) Set(name string, value interface{}) *Scope {
	scope.db.InstantSet(name, value)
	return scope
}

scope.Setが何をしているのか、というとこちらはscope.db(このdbはgorm.DBのインスタンス)のInstanceSetを呼び出している。

// InstantSet instant set setting, will affect current db
func (s *DB) InstantSet(name string, value interface{}) *DB {
	s.values[name] = value
	return s
}

gorm.DBのInstanceSetは、gorm.DBがもつmap[string]interface{}型のマップに、値をセットしているだけである。なお、このgorm.DBはNewScopeを呼ばれた時に元のgorm.DBからcloneされたものなので、ここのアクションはアプリ側(gormの外側)で参照しているgorm.DBインスタンスには影響しない。

return scope

beginTransactionメソッドは、最後に自身のインスタンスを返す。

func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope {
	for _, f := range funcs {
		(*f)(scope)
		if scope.skipLeft {
			break
		}
	}
	return scope
}

すると、先ほど見ていたscope.callCallbacksの方に処理が戻る。今までの処理の中に、scopeのskipLeftが変更される箇所はなかったので、次のコールバックが呼ばれることとなる。

引き続き、コールバックの中身を見ていこう…

次の記事: GORMのソースコードを読む4 – Createその3

Pocket

「GORMのソースコードを読む3 – Createその2」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です