【Go言語】Ginフレームワークとsqlxを連携させた簡易プログラム例→sqlx中心です。

Go言語の[Ginフレームワーク](github.com/gin-gonic/gin)と、同じくGo言語のデータベース処理用ライブラリ[sqlx](github.com/gin-gonic/gin)を連携させた実装例を今回お見せしたいと思います。

「[gorm](https://github.com/go-gorm/gorm)ネタは多いけど、sqlxネタは少ないな」と思ってましたので。

sqlxの使い方にクローズアップさせてまとめました。


### (2022/6/19) 続編作りました。

SelectとGetの違い、バルクインサート、トランザクションについてまとめました。

https://qiita.com/rikimaru-odd-person/items/4151b6c94c0f48882e9c



### 始めに、おことわり
 - ｢使えればいい｣レベルでの内容ですので、セキュリティやコード品質は無視しております。<br />ご了承ください。
 - クリーンアーキテクチャではなく、ベタベタな`Controller＋Model`形式での設計であることをご容赦ください。
 - `Docker` + `docker-compose`を用いた環境ですが、今回は省略させていただきます。

### 例題のソースを共有しました
https://github.com/rikimaru-tokyo/gin_sqlx_lesson01

### 環境
 - Oracle Virtual Box for Windows 6.1 (OS:Ubuntu Server 20.04LTS)
 - Docker Ver.20.10.17
 - docker-compose Ver.2.5.1
 - Go  Ver.1.18.3
 - Gin Ver.1.8.1
 - go-sql-driver/mysql Ver.1.6.0
 - jmoiron/sqlx Ver.1.3.5
 - mysql  Ver.8.0.29

### ディレクトリ構成
```tree

/gin_sqlx_lesson01
├── controller
├── docker
├── docker-compose.yml
├── main.go
├── model
│   ├── members.go      //←今回ご紹介する例
│   └── ranks.go        //←今回ご紹介する例
└── myDatabase
    └── myDatabase.go   //←今回ご紹介する例
```

### 1: DB接続
"MYSQL_USER"などの環境変数を用いて、DSN(Data Source Name)を定義します。

そして、`sqlx.Open`というメソッドでデータベースに接続します。

```myDatabase/myDatabase.go

package myDatabase

import (
	"fmt"
	"os"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

// DbInit() データベースに接続する。
func DbInit() *sqlx.DB {

	//dsn形式：username:password@protocol(address)/dbname?param=value
	dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true&loc=Asia%%2fTokyo",
		os.Getenv("MYSQL_USER"), 
        os.Getenv("MYSQL_PASSWORD"), 
        os.Getenv("MYSQL_HOST"), 
        os.Getenv("MYSQL_DATABASE"),
	)

	//MySQLに接続
    //sqlx.Openの第1引数は固定値でDBの種類を入力(今回は「mysql」)、第2引数は固定値でDSNを入れる。
	conn, err := sqlx.Open("mysql", dsn)

	if err != nil {
		panic(err)
	}

    //接続オブジェクトのポインタを返します。
	return conn
}
```

### 2: モデル(テーブルデータをすべて取得する例)

```model/ranks.go
package model

import (
	"time"
	"github.com/rikimaru-tokyo/gin_sqlx_lesson01/myDatabase"
)

//データの受け手となる構造体を宣言
//`db`タグは必須。
//タグの値はDBテーブルのカラム名に合わせること。
type RankResult struct {
	ID        int        `db:"id"`
	Name      string     `db:"name"`
	CreatedAt *time.Time `db:"created_at"`
	UpdatedAt *time.Time `db:"updated_at"`
}

// GetRanksAll ranksテーブルデータ全出力
func GetRanksAll() ([]RankResult, error) {
	//データベース接続
	db := myDatabase.DbInit()

	//SQL文字列定義
	sql := `SELECT * FROM ranks;`

	//データの受け手となる構造体をポインタにしてSelect()の第1引数に渡す。
	//Select()の第2引数はSQL文字列をそのまま渡す。
	//第3引数以降は、プレースフォルダのパラメータなどを入れる。
	var ranks []RankResult
	err := db.Select(&ranks, sql)

	if err != nil {
	 	return nil, err
	}

	return ranks, nil
}
```
### 3: モデル(プレースフォルダ「?」の使用例)
安全にSQLを実行するために、プレースフォルダ「?」に変数を投入する例です。

ポイントは`Select()`メソッドの第3引数以降に、プレースフォルダ「?」がついた順番にあわせてパラメータ変数を入れておくことです。

```model/members.go
package model

import (
	"github.com/rikimaru-tokyo/gin_sqlx_lesson01/myDatabase"
)

//データの受け手となる構造体を宣言
type MembersResult struct {
	MemberID   int     `db:"member_id"`
	MemberName string  `db:"member_name"`
	Rank       string  `db:"rank_name"`
}


// GetMembersWithRanks テーブル結合かつプレースフォルダを使った結果を取得
func GetMembersWithRanks(memberId int, rankName string)([]MembersResult, error){
	//データベース接続
	db := myDatabase.DbInit()

	//SQL文字列定義
	sql := `SELECT 
				m.id as member_id, m.name as member_name, r.name as rank_name
			FROM
				members as m 
			INNER JOIN
				ranks as r ON m.rank_id = r.id
			WHERE
				m.id = ? OR r.name = ?;
	`
	var member []MembersResult

	// 第3引数以降は、プレースフォルダ「?」がついた順番にあわせてパラメータ変数を入れておく。
	err := db.Select(&member, sql, memberId, rankName)

	if err != nil {
	 	return nil, err
	}

	return member, nil
}

```
### 4: モデル(プレースフォルダ「:変数名」の使用例)
プレースフォルダ「?」の数が多すぎる、区別がつきにくいという場合には「:変数名」を使った定義も可能です。

ポイントはSQL上の「:変数名」に合わせた、`map[string]interface(連想配列)`をあらかじめ用意して、`NamedExec`メソッドの第2引数に投入することです。


```model/ranks.go
// InsertRank() プレースフォルダ「:変数名」として使いたい場合
// ranksテーブルに{name:"Platinum"}を新規登録します。
func InsertRank() (int, error) {
	db := myDatabase.DbInit()
	now := time.Now()

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

	// 「:変数名」という形式でプレースフォルダを使いたい場合、NamedExecというメソッドを使います。
	//  第1引数はSQL文字列、第2引数は「map[string]interface(連想配列)」
	//  連想配列インデックス名は、SQL文中の「:変数名」に合わせます。
	//      [参考資料: https://github.com/jmoiron/sqlx の READMEより]
	rows, err := db.NamedExec(sql,  map[string]interface{}{
		"id":4, "name": "Platinum", "cr": now, "up": now,
	})
	if err != nil {
		return 0, err
    }

    // INSERT件数カウント
    r, err := rows.RowsAffected()
	if err != nil {
		return 0, err
    }
    return int(r), nil
}
```


----
まだまだ足りないと思いますが、今回は以上となります。