0
Help us understand the problem. What are the problem?

posted at

updated at

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

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

gormネタは多いけど、sqlxネタは少ないな」と思ってましたので。

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

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

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

始めに、おことわり

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

例題のソースを共有しました

環境

  • 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

ディレクトリ構成


/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
}

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

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?