gorm version1からversion2にアップデートする

最終更新:2020/09/18


GolangでデファクトライブラリになっているORM、gormのversion2がリリースされました。

早速version2にアップデートしましたが、version1からversion2への変更は破壊的な変更を含んでいて、ただ単にライブラリをupdateしただけでは既存のアプリケーションが動かなくなります。

この記事を読んでいる開発者の中にも、少し試してみて

「これは苦労しそうだ」

とアップデートすべきか悩んでいる人もいるのでないでしょうか。

開発において、アップデートをするかどうか悩むポイントは以下だと思います。

  • verison1とverison2の違い
  • アップデートにかかる工数
  • 影響がでる点
  • テストコードで使っている場合は、sqlmockの修正

この記事では、これらの疑問を解決します。

目次

  1. verison1とverison2の違い
  2. アップデートにかかる工数
  3. 影響のある点

    1. count文
    2. sql文
    3. structを使ったwhere文
    4. rawを使ってLimit, offsetのチェーンができない
    5. Limit, offsetの仕様変更
    6. insert, updateのカラム順の変更
    7. ErrRecordNotFound
  4. sqlmockの修正と方法
  5. version1の削除

1. verison1とverison2の違い

verison2ではversion1ではなかった機能が、多く追加されています。

実際の開発で特に役立ちそうな新機能は

  • Batch Insert
  • 複数データベースのオフィシャルプラグイン
  • DBをread/writeに分割

です。

Batch InsertとDBのread/writeの分割は、大規模アプリケーションの場合は必要になるので、嬉しいですね。

また、verison2はverison1のコード更新ではなくリライトで、パッケージ自体が変更されています。

なので、コードを書き換える場合は import文から変更が必要になります。

パッケージ自体が異なるので、両方のversionを利用することも可能です。

2. アップデートにかかる工数

アップデートにかかる工数は、プロジェクトの規模や採用しているアーキテクチャーによりますが、

最初の予測より多め

に見積もっておいたほうが良いと思います。

verison1とverison2では、微妙に異なるクエリが発行されるので、sqlmockなどを使ってテストでsqlを確認している場合は、テストコードの書き直しが発生します。

また、version1では許された処理がverison2では不可能になっているパターンもあります。

特にraw関数で生SQLを扱っている場合は、修正が必要になる可能性が高いので、注意が必要です。

3. 影響のある点

gorm1からgorm2にupdateするにあたって、影響が出る箇所をまとめました。 他にも沢山あると思いますが、ここに記載した箇所は多くのプロジェクトでも遭遇するでしょう。

3-1. count文

count文を発行するcountメソッドが変わりました。 version1では、引数にinterface型を宣言していました。

func (s *DB) Count(value interface{}) *DB {}

version1では、型としてinterfaceを使っていました。 型にinterfaceを使うことで、型の制約を受けなくなります。

しかし、count文を発行したSQLで受け取るのは数値であることが確定しているので、意味のないinterface型の宣言だと言えるでしょう。

これがversion2だとint64型に変更されました。

func (db *DB) Count(count *int64) (tx *DB) {}

数値型に型が固定されました。 これは正しい変更だと思います。

一方で

int64型

で宣言されているので、intを使って値を受け取る処理を記述していた場合は、実装の変更が必要になります。

配列のlen関数と比較チェックなどをしている場合は、len関数をint64(len(hoge))のように変更する必要があります。

count文の実装が多いプロジェクトで、テストコードまで含めると結構な手間になります。

3-2. sql文

gorm1とgorm2では、発行されるsqlが若干変わります。

例えば、version1でusersテーブルからidでデータを取得するsql文は

SELECT * FROM `users` WHERE (id = ? and deleted_at is not null )

が発行されました。

これがversion2では

SELECT * FROM `users` WHERE id = ? AND `users`.`deleted_at` IS NULL

に変更されていました。

取得結果は同じですが、見ての通り、where文の順番、カッコやバッククオートの有無が異なっています。

他にもcount文がcount(*)からcount(1)になるなど、細かい修正が所々で行われています。

3-3. structを使ったwhere文

where文の条件をstructで実装している人も多いと思います。

version1で以下のようなコードで、where文が生成できました。

func (p *userRepositoryImpl) CountByQuery(user *model.User) (int64, error) { var count int64 if err := p.db.Model(&model.User{}).Where(&user).Count(&count).Error; err != nil { return -1, err } return count, nil }

Whereに渡すuserのオブジェクトは、ポインタやアドレスでも構いません。

これは、version2では動きません。

version2で動かすには、

func (p *userRepositoryImpl) CountByQuery(user *model.User) (int64, error) { var count int64 if err := p.db.Model(&model.User{}).Where(user).Count(&count).Error; err != nil { return -1, err } return count, nil }

のようにポインタに修正する必要があります。

version2は型に対して厳しくなっています。

3-4. rawを使ってLimit, offsetのチェーンができない

version1ではraw関数に生SQLを書いて、Limit, offsetを関数をチェーンでつなぐことができました。

sql := p.db.Raw("select * from users") sql = sql.Limit(10) sql = sql.Offset(0)

しかし、version2ではRaw関数を使った場合、チェーンはできなくなりました。 チェーンで繋いでSQLを作成したい場合は、Raw関数に生sqlを書くのでなく、Raw関数以外の関数を使ってチェーンで繋ぐ必要があります。

3-5. Limit, offsetの仕様変更

Limit, offsetは数値型になりました。

version1はなぜか数値型だったので、良い変更です。

また、offsetの値が0の場合はsqlに offset 0 と出力されるのではなく、非表示になりました。

これも無駄なクエリだったので、良い変更です。

3-6. insert, updateのカラム順の変更

insert, update文のカラム順が変わりました。

version2では、カラム名idがvalues文、set文の最後に指定されます。

3-7. ErrRecordNotFoundの変更

データが存在しない場合のエラー処理も変更になりました。

version1では、以下のようにロジックを実装しました。

import ( "github.com/jinzhu/gorm" ) if gorm.IsRecordNotFoundError(error) { // something }

version2では以下のように書き直します。

import ( "errors" "gorm.io/gorm" ) if errors.Is(dbError, gorm.ErrRecordNotFound) { // something }

errorsのIs関数でエラーがErrRecordNotFoundがどうかを判定します。

4. sqlmockの修正と方法

sqlmockを使っている場合は、version1とversion2では実装方法が変わります。

version1ではsqlmockを使う場合、以下のようにコードを書きました。

db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } dbClientMock, err := gorm.Open("mysql", db) if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) }

version2でsqlmockを使う場合、以下のようにコードを書き換えます。

db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } dbClientMock, err := gorm.Open( mysql.Dialector{Config: &mysql.Config{DriverName: "mysql", Conn: db, SkipInitializeWithVersion: true}}, &gorm.Config{}, ) if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) }

5. version1の削除

全てのコードを修正したらターミナルで

go tidy -v

を実行します。

go tidyは不要なpackageを削除するコマンドです。

version1が使われている箇所が完全に修正されていれば、ライブラリが自動で削除されます。

関連記事