(続) Ginフレームワークとsqlxを連携させた簡易プログラム例 (SelectとGetの違い、バルクインサート、トランザクション)

[前回の記事](https://qiita.com/rikimaru-odd-person/items/519053f24c20a3344f9c)に続いて、下記3つの使用方法についてお見せしたいと思います。

 - SelectとGetの違い
 - バルクインサート
 - トランザクション

### 前回の記事
https://qiita.com/rikimaru-odd-person/items/519053f24c20a3344f9c


### 始めに、おことわり
 - ｢使えればいい｣レベルでの内容ですので、セキュリティやコード品質は無視しております。ご了承ください。
 - クリーンアーキテクチャではなく、ベタベタなController＋Model形式での設計であることをご容赦ください。


### 環境とディレクトリ構成
前回と同じです


### 例題のソース
https://github.com/rikimaru-tokyo/gin_sqlx_lesson02

### 1: Select()とGet()の違い
- Select() : 複数行のデータ取得時に使用する。
- Get() : 単数行データ取得時に使用する。
  - データが見つからない場合はエラーを出すので注意。
  - `SELECT COUNT ～`や`SELECT EXISTS ～`のケースに使いやすい。
  

```model\ranks.go
// UseSelect Selectメソッドを使ったデータ取得
func UseSelect(memberId int) ([]MembersResult, error) {
	//データベース接続（前回の記事のDB接続ソースを使っています）
	db := myDatabase.DbInit()

	sql := `SELECT id, name FROM members WHERE id >= ?;`

	var member []MembersResult

	// Select()は複数行のデータ取得時に使用する。
	err := db.Select(&member, sql, memberId)
	if err != nil {
		return nil, err
	}

	return member, nil
}


// UseGet Getメソッドを使ったデータ取得
func UseGet(memberId int) (bool, error) {
	//データベース接続（前回の記事のDB接続ソースを使っています）
	db := myDatabase.DbInit()
	sql := `SELECT EXISTS (SELECT id FROM members WHERE id = ?);`

	// Get()は単数データ取得時に使用する。
	// データが見つからない場合はエラーを出すので注意。
	// 「SELECT COUNT」や「SELECT EXISTS」のケースに使いやすい。
	var exists bool
	err := db.Get(&exists, sql, memberId)
	if err != nil {
		return false, err
	}

	return exists, nil
}

```

### 2: バルクインサート
プレースフォルダと`[]map[string]interface{}`を利用して、バルクインサートができます。

ポイントはSQLで定義したパラメータ名と、`[]map[string]interface{}`のインデックス名をもれなく合わせておくことです。


```model\ranks.go
// InsertRank() ranksテーブルにバルクインサートを実行する。
func InsertRank() (bool, error) {
	//データベース接続
	db := myDatabase.DbInit()
	now := time.Now()

	sql := `INSERT INTO ranks 
				(id, name, created_at, updated_at)
			VALUES
				(:id, :name, :cr, :up);`

	//SQLで指定されているパラメータと、連想配列の要素をもれなく統一させること。
	inserts := []map[string]interface{}{
		{"id": 4, "name": "Platinum", "cr": now, "up": now},
		{"id": 5, "name": "Diamond", "cr": now, "up": now},
	}

	// SQLを実行する。
	rows, err := db.NamedExec(sql, inserts)
	if err != nil {
		return false, err
	}

	// INSERT件数カウント
	r, err := rows.RowsAffected()
	if err != nil || r < 1 {
		return false, err
	}

	return true, nil
}
```

### 3: トランザクション
`Beginx()`でトランザクションを開始しsqlx.Txポインタを受け取り、sqlx.Txポインタを元に`NamedExec`でSQLを実行します。
処理に失敗した場合は`Rollback()`を呼び出し、処理に成功した場合は`Commit()`します。

```model\ranks.go
// DeleteRank() ranksテーブルからデータを2件削除する。トランザクションを用いる。
func DeleteRank() (bool, error) {
	//データベース接続
	db := myDatabase.DbInit()
	sql := `DELETE FROM ranks WHERE name = :del1 OR name = :del2;`
	// トランザクション開始(sqlx.Txポインタを受け取る)
	transaction, err := db.Beginx()
	if err != nil {
		return false, err
	}

	// transactionを元にSQLを実行する。
	deletes := map[string]interface{}{
		"del1": "Platinum",
		"del2": "Diamond",
	}
	rows, err := transaction.NamedExec(sql, deletes)
	if err != nil {
		//実行に失敗した場合はロールバックさせ、トランザクションを終了させる。
		_ = transaction.Rollback()
		return false, err
	}

	// 成功した場合はコミットさせ、トランザクションを終了させる。
	if err := transaction.Commit(); err != nil {
		return false, err
	}

	// DELETE件数カウント
	r, err := rows.RowsAffected()
	if err != nil || r < 1 {
		return false, err
	}

	return true, nil
}

```


#### トランザクション補足
上記の例で使用した`Beginx()`のほかに`MustBegin()`というメソッドも使えます。
ただし`MustBegin()`で始めた場合は、`NamedExec()`は使えず`MustExec(SQL文)`を使用しなくてはなりません。


(作者のブログ「Transaction」の章より)

https://jmoiron.github.io/sqlx/

-----
以上となります。