3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MySQLのDuplicate Entryのエラーを簡単にエラーハンドリングする方法[Go]

Posted at

DB設計において、主キーを設定して重複を防ぐことがあると思います。

そこからアプリケーションでDBからデータを取り出して、データを整形して、ハンドラーでJSON等でデータを返したりすると思います。

その中でもエラーの内容によって、JSONによって表示させるエラーの内容を分岐させたいという時に、Goでは下のようにエラーハンドリングを行うと思います。

if err != nil {
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, custom_error.NewNotFoundError(custom_error.NotFoundRecord)
	}
	return nil, custom_error.NewInternalServerError(custom_error.InternalErrorInternal, err.Error())
}

例えば、gormでのデータベース操作でデータが見つからなかったときにgorm.ErrRecordNotFoundというカスタムエラーを返してくれます。

そのエラーを受けとったら、JSONで返す内容のテンプレートを使用して、フロントエンドにレコードがないことをわかりやすく伝えます。

上の例ではレコードが存在しないErrNotFoundですが、それ以外にもデータの重複等のエラーを出すことが必要になってくると思います。

gormの先ほど紹介したカスタムエラーには、入力データがすでにあるデータであることを表すDuplicate entryを表すエラーがないので、別の方法でエラーハンドリングをする必要があります。

今回は、そのようなORMライブラリ等で準備されていないエラーを対処する方法を紹介したいと思います。

mysqlパッケージのMySQLErrorを使う

Duplicate entryのエラーコードは1062です。
このエラーコードを簡単に取り出す方法として、goの標準パッケージにmysql向けのエラーコードやメッセージを扱いやすくするMySQLErrorの構造体が準備されています。

アプリケーションコード内での使用例

アプリケーションコード内では、返ってきたエラーをMySQLErrorに変換してエラーハンドリングを行います。

var mysqlErr *mysql.MySQLError
if ok := errors.As(err, &mysqlErr); ok {
		if mysqlErr.Number == 1062 {
			return repository.ErrDuplicateEntry
		}
}

mysql.MySQLError型の変数を準備して、errorsパッケージのAsメソッド使用してエラーをmysql.MySQLError型の変数に格納しています。

errors.Asについて

Asメソッドは、エラーが別の第二引数に指定してる変数に代入可能かどうかboolとして結果を返しつつ、第二引数に結果を代入します。

var pathError *fs.PathError
if errors.As(err, &pathError) {
	fmt.Println("Failed at path:", pathError.Path)
} else {
	fmt.Println(err)
}

このもう一つ別のメソッドとしてIsメソッドが使われることが多いです。

if errors.Is(err, fs.ErrNotExist) {
	fmt.Println("file does not exist")
} else {
	fmt.Println(err)
}

Isメソッドは、エラ-が同じかどうかを調べることができます。
エラーは、エラーチェーンと呼ばれるエラーをどんどんラップしていくことができ、子メソッドで起きたエラーも親メソッドで見ることができます。

テストコードでの使用例

テストコードで該当エラーが返ってくるかどうかを調べるには下のように使います。

wantErr := &mysql.MySQLError{
				Number:  1452,
				Message: "Cannot add or update a child row: a foreign key constraint fails (`report_generator_test`.`blueprints`, CONSTRAINT `fk_users_blueprints` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))",
}

err := repo.Create(ctx, value)

assert.ErrorIs(t,err,wantErr)

testifyパッケージのassertのErrorIsでMySQLのエラーコードを扱いたいときには、MySQLError型で変数を作成します。

ErrorIsの引数にはerrorインターフェースが満たされているものであれば設定することができます。

MySQLErrorはerrorインターフェースを実装しているため、引数として当てることができます。

まとめ

MySQLErrorを使用することでORMライブラリに標準で準備されていないエラーを簡単にハンドリングすることができます。

こうすることで、string型のエラーメッセージの一致でハンドリングするなどの難しいことを考えず、エラーコード等で簡単にハンドリングできるようになるのでおすすめです。

ぜひ参考にしてください。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?