SE_BOKUのまとめノート的ブログ

SE_BOKUが知ってること・勉強したこと・考えたことetc

GO言語(golang)1.20/sqlxでMariaDB(MySQL)データベースを使う

 f:id:arakan_no_boku:20210412005751p:plain

目次

GO言語(golang)1.20/sqlxでMariaDB(MySQL)データベースを使う

GO言語(golang)でMariaDBMySQL)を使います。

DBはインストールしてある前提。

環境はWindows10/VSCodeです。

パッケージは「sqlx」を使います。 

github.com

ドライバのインストール

GO言語用のドライバをインストールします。

github.com

go getの構文をサンプルに書いておきます。

go get -u github.com/go-sql-driver/mysql

依存関係があるモジュールを更新して、新しいマイナーリリースまたはパッチリリースを使用する「-u」オプションをつけてます。

go.modがあるフォルダで実行しないとエラーになります。

importは

_ "github.com/go-sql-driver/mysql"

です。

前に「_」が必要なのが注意点です。

これをつけずに普通にインポートすると、ドライバの機能が動きません。

この「_」は、importするパッケージ内に定義されている「init()」をインポート時に実行させる指定です。

sqlxのインストール

sqlxも初回はインストールが必要です。

go getの構文をサンプルに書いておきます。

go get -u github.com/jmoiron/sqlx

インポートは

"github.com/jmoiron/sqlx"

です。

この後、sqlxを使っていくつか試してみます。

サンプルや情報は下記サイトにも書いてあります(英語ですが)。

pkg.go.dev

試す(1):データベースオープン

データベースオープンの構文は以下です。

dbx, err := sqlx.Open("mysql", "user_id:password@tcp(localhost:3306)/dbname")

引数は

  • user_id:DBユーザID
  • password:DBパスワード
  • @tcp(localhost:3306):@プロトコル(host:port)
  • dbname:データベース名

です。

例えば、DBが「bokudb」、ユーザが「bokuuser」、パスワードが「bokupass」、そしてDBがあるのが「localhost」なら以下のようになります。

dbx, err := sqlx.Open("mysql", "bokuuser:bokupass@tcp(localhost:3306)/bokudb")

以後、この構文で取得した「dbx」を使ってサンプルを書いていきます。

試す(2):Select

Selectを使います。

こんな感じのSQL

SELECT id,name FROM gotest

を実行してみます。

まずは、結果を格納する構造体を定義します。

取得する「id」が数値・「name」が文字列なら、テーブルのカラム名の先頭を大文字にして構造体のメンバの名称にして定義します。

type Results struct {
    Id int
    Name string
}

その構造体を以下のように「rows」変数に代入して

rows := Results{}

その変数のポインタを「&rows」のように渡して実行します。

dbx.Select(&rows, "SELECT id,name FROM gotest")

これで実行結果が「rows」配列に格納されます。

あとは、結果を格納した構造体配列を「for ・・range」で1レコードずつ処理します。

for i, v := range rows {
        fmt.Printf("%d:%d/%s\n", i, v.Id, v.Name)
 }

試す(3):Queryxを使ったSELECT

同じSQL文を「Queryx」を使ってみます。

Selectを使う場合とは結果を格納する構造体変数の使い方が違います。

Queryxを使ったサンプルは以下のようになります。

rows := Results{}
results, _ := dbx.Queryx("SELECT id,name FROM gotest")

for results.Next() {
       e := results.StructScan(&rows)
       if e == nil {
            fmt.Printf("%d/%s\n", rows.Emp_id, rows.Addr)
        }
 }

さっきは「 Results{}」と構造体の配列にしていましたが、今回は配列にしません。。

rows := Results{}

Selectの場合はクエリ実行時点で複数件の結果を一気に構造体配列に格納して、それをループさせて処理しますが、Queryxの場合は構造体へのマッピングは1行ずつ行うので配列である必要はないからです。

Queryxの場合、クエリの処理結果は「results」の部分に格納されます。

results, _ := dbx.Queryx("SELECT id,name FROM gotest")

resultsは「*Rows」で「Next()」を使って1行ずつ取り出すことができます。

for results.Next() {

そして取り出した1行ずつ結果構造体へ「StructScan」でセットします。。

 e := results.StructScan(&rows)

これで「e」がnilだったら、正常に構造体に結果がセットされています。

試す(4):プレースホルダ

SQLインジェクション攻撃などを防ぐために、「プレースホルダ」を利用します。

上記例のSELECT文にWHERE句をつけてプレースホルダを試します。

実行するSQLは以下です。

SELECT id,name FROM gotest WHERE id >= 1 AND id <= 5

この「id >= 1 and id <= 5」の「1」と「5」を、それぞれ変数「low」と「high」にセットしてプレースホルダ「?」を適用して、前の2つの例を書き直します。

変数は

low := 1

high := 5

と定義します。

その場合「Select」は

dbx.Select(&rows, "SELECT id,name FROM gotest WHERE id >= ? AND id <= ?", low, high)

となり、Queryxの場合は

results, _ := dbx.Queryx("SELECT id,name FROM gotest WHERE id >= ? AND id <= ?", low, high)

となります。

ちなみに、ネットに転がっているサンプルを見ていると「?」を使っている場合と、「$1や$2」を使っている場合の2パターンがあります。

僕はMariaDBなので「?」を使ってます。

試しに「?」部分を「$1・$2」に書き直したら、うまく動きませんでした。

これが不思議だったので調べてたら、以下のような記述があるページがありました。

Bindvars are therefore database specific:

  • MySQL uses the ? variant 
  • PostgreSQL uses an enumerated $1, $2, etc bindvar syntax
  • SQLite accepts both ? and $1 syntax
  • Oracle uses a :name syntax

訳すと

バインドバーはデータベース固有です:

  • MySQLは「?」
  • PostgreSQL は列挙された $1, $2 などのバインドバー構文を使用します。
  • SQLiteは?構文と$1構文の両方を使用できます。
  • Oracle は :name 構文を使用します。

みたいな感じなんですが、案外これが答えなのかもな・・と思ってます。

試す(5):トランザクション 

INSERT,UPDATE,DELETEなどの更新操作はトランザクションを使います。

トランザクションを開始するには「MustBegin」を使うのが簡単です。。

tx := dbx.MustBegin()

Beginxでもいいですが、

tx, err := dbx.Beginx()

戻り値のエラーをチェックしてエラー処理を自分で書く必要があります。

MustBeginはエラーがあればpanic()の部分もやってくれるので、若干楽・・です。

あとは、MustBeginの戻り値「tx」を使って、MustExecで、各SQL文を実行します。

  • tx.MustExec("insert into gotest values(?,?)", id, name+strconv.Itoa(id))
  • tx.MustExec("update gotest set name=? where id < 5", "IDが5より小さい")
  • tx.MustExec("delete from gotest")

Execではなく、MuxtExecを使っている理由はBeginの時と同じです。

エラー処理をおまかせして楽をしているだけです。

トランザクション中にエラーが発生すればロールバックします。

そして、トランザクションの最後にコミットして、データを確定させます。

tx.Commit()

こんな感じです。

ざっとですが、sqlxを使ったサンプルをやってみました。

ではでは。