Build Web Application With Golang を試してやってみる 18

はじめに

前回の記事の続きです。
前回の記事はこちら

MySQL データベースの使用

今回は MySQL のデータベースをGoで使用する手順についてです。

環境構築

今回からは、Docker を使用して実行環境を用意します。

  1. 下記のリポジトリをクローンしてください。
    https://github.com/katsuobushiFPGA/docker-golang-db-for-develop.git

  2. 任意のディレクトリにクローンした後、Docker コンテナを立ち上げます。

docker-compose up -d
  1. 立ち上がったら、workspace コンテナに入ります。
docker-compose exec workspace bash
  1. main.go を サンプルとして作成して、下記を実行します。
go run main.go
// サンプル用
package main

import "fmt"
func main() {
    fmt.Println("Hello, World")
}

構成について

このリポジトリは下記のような構成になっております。

workspace コンテナ… Go を実行するコンテナ

mysql コンテナ… mysql を実行するコンテナ

phpmyadminコンテナ…phpmyadmin を実行するコンテナ

さて、これで MySQL を試せる環境がやってきました。
早速読んでいきましょう。

MySQL ドライバ

MySQLドライバ もいくつかあるので、その中の一つを使っていくようです。

https://github.com/go-sql-driver/mysql database/sql をサポートしており、すべて go で書かれています。

同じものを使っていきましょう。

CREATE DATABASE `gotest`;
USE `gotest`;
CREATE TABLE `userinfo` (
    `uid` INT(10) NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(64) NULL DEFAULT NULL,
    `departname` VARCHAR(64) NULL DEFAULT NULL,
    `created` DATE NULL DEFAULT NULL,
    PRIMARY KEY (`uid`)
);

CREATE TABLE `userdetail` (
    `uid` INT(10) NOT NULL DEFAULT '0',
    `intro` TEXT NULL,
    `profile` TEXT NULL,
    PRIMARY KEY (`uid`)
);

同じテーブルを作成してみます。

Go modules を使いたいのでセットアップしておきます。

$ go mod init k-bushi.com/example
go: creating new go.mod: module k-bushi.com/example

Table を作成したいので、phpMyAdminmysql コンテナに入って SQL を実行します。
今回はmysqlコンテナに入ります。

$ docker-compose exec mysql bash
## mysql -u root -proot

SQLを実行します。

結果

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| gotest             |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.01 sec)

mysql> use gotest;
Database changed
mysql> show tables;
+------------------+
| Tables_in_gotest |
+------------------+
| userdetail       |
| userinfo         |
+------------------+
2 rows in set (0.01 sec)

できました!

そしたら、下にあるコードを main.go に貼り付けてみます。

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "database/sql"
    "fmt"
    //"time"
)

func main() {
    db, err := sql.Open("mysql", "root:root@tcp(mysql:3306)/gotest?charset=utf8")
    checkErr(err)

    //データの挿入
    stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
    checkErr(err)

    res, err := stmt.Exec("astaxie", "研究開発部門", "2012-12-09")
    checkErr(err)

    id, err := res.LastInsertId()
    checkErr(err)

    fmt.Println(id)
    //データの更新
    stmt, err = db.Prepare("update userinfo set username=? where uid=?")
    checkErr(err)

    res, err = stmt.Exec("astaxieupdate", id)
    checkErr(err)

    affect, err := res.RowsAffected()
    checkErr(err)

    fmt.Println(affect)

    //データの検索
    rows, err := db.Query("SELECT * FROM userinfo")
    checkErr(err)

    for rows.Next() {
        var uid int
        var username string
        var department string
        var created string
        err = rows.Scan(&uid, &username, &department, &created)
        checkErr(err)
        fmt.Println(uid)
        fmt.Println(username)
        fmt.Println(department)
        fmt.Println(created)
    }

    //データの削除
    stmt, err = db.Prepare("delete from userinfo where uid=?")
    checkErr(err)

    res, err = stmt.Exec(id)
    checkErr(err)

    affect, err = res.RowsAffected()
    checkErr(err)

    fmt.Println(affect)

    db.Close()

}

func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

Docker で立てた mysql コンテナに接続するので接続先を修正したりしてます。

実行結果です!

bash-5.0# go run main.go
1
1
1
astaxieupdate
研究開発部門
2012-12-09
1
bash-5.0#

やったぜ!
また読みすすめていきます。

キーとなるいくつかの関数についてご説明します:
sql.Open()関数は登録済みのデータベースドライバを開くために使用されます。
go-sql-driver の中で mysql のデータベースドライバを登録し、2つ目の引数は DSN(Data Source Name)です。
これは go-sql-driver が定義するデータベース接続と設定情報です。以下のシンタックスをサポートします:

なるほど?
接続先を変更する必要がありましたので、個々は変更しましたね!

user@unix(/path/to/socket)/dbname?charset=utf8
user:password@tcp(localhost:5555)/dbname?charset=utf8
user:password@/dbname
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

db.Prepare()関数は sql 操作を実行するプリペアードステートメントを返すために用いられます。
その後、準備完了の実行状態を返します。
db.Query()関数は直接 Sql を実行し Rows 結果を返すために使われます。
stmt.Exec()関数は stmt が用意された SQL 文を実行するために用いられます。
渡される引数がどれも=?に対応するデータであることがわかるかとおもいます。このような方法である程度 SQL インジェクションを防止することができます。

このあたりはどの言語でも共通ですね。
↑ プリペアードステートメント
覚えておくと、どの言語でも DB の操作はすんなりいけそうです!

今回はここで終わり・・・ではなく、 phpMyAdminINSERTされているのかや UPDATE されているのかは確認していませんね?
ここを確認しましょう。

サンプルではデータを削除してしまっているので、
INSERT × 10 回 (id1~id10)
UPDATE (id:1)
DELETE (id:2)
SELECT (全部)
を順に実行して、最後どうなっているのかを確認したいと思います。
サンプルを改造しましょう!

まずは、mysql コンテナに入って、下記を実行します。(一旦全部消します。)

mysql> truncate userdetail;
Query OK, 0 rows affected (0.05 sec)

mysql> truncate userinfo;
Query OK, 0 rows affected (0.03 sec)

mysql>

消えたことの確認 (userdetail は使っていませんが。。) phpmyadmin-01 phpmyadmin-02

そして改造しました。

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "database/sql"
    "fmt"
    //"time"
)

func main() {
    db, err := sql.Open("mysql", "root:root@tcp(mysql:3306)/gotest?charset=utf8")
    checkErr(err)

    //データの挿入
    stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
    checkErr(err)

	var res sql.Result
	for i := 0; i < 10; i++ {
		res, err = stmt.Exec("astaxie", "研究開発部門", "2012-12-09")
		checkErr(err)

		id, err := res.LastInsertId()
		checkErr(err)
	    fmt.Println("LastInsertId:", id)
	}

    //データの更新
    stmt, err = db.Prepare("update userinfo set username=? where uid=?")
    checkErr(err)

    res, err = stmt.Exec("Update(id:1)", 1)
    checkErr(err)

    affect, err := res.RowsAffected()
    checkErr(err)

    fmt.Println("RowsAffected", affect)

	//データの削除
    stmt, err = db.Prepare("delete from userinfo where uid=?")
    checkErr(err)

    res, err = stmt.Exec(2)
    checkErr(err)

    affect, err = res.RowsAffected()
	checkErr(err)

    //データの検索
    rows, err := db.Query("SELECT * FROM userinfo")
    checkErr(err)

    for rows.Next() {
        var uid int
        var username string
        var department string
        var created string
        err = rows.Scan(&uid, &username, &department, &created)
        checkErr(err)
        fmt.Println(uid)
        fmt.Println(username)
        fmt.Println(department)
        fmt.Println(created)
    }

    fmt.Println("RowsAffected", affect)
    db.Close()

}

func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

どどん!

bash-5.0# go run main.go 
LastInsertId: 1
LastInsertId: 2
LastInsertId: 3
LastInsertId: 4
LastInsertId: 5
LastInsertId: 6
LastInsertId: 7
LastInsertId: 8
LastInsertId: 9
LastInsertId: 10
RowsAffected 1
1
Update(id:1)
研究開発部門
2012-12-09
3
astaxie
研究開発部門
2012-12-09
4
astaxie
研究開発部門
2012-12-09
5
astaxie
研究開発部門
2012-12-09
6
astaxie
研究開発部門
2012-12-09
7
astaxie
研究開発部門
2012-12-09
8
astaxie
研究開発部門
2012-12-09
9
astaxie
研究開発部門
2012-12-09
10
astaxie
研究開発部門
2012-12-09
RowsAffected 1
bash-5.0# 

phpmyadmin-03 phpmyadmin-04

おぉ~!
ちゃんとうまく行ってますね。

おわりに

明日は posgresql ですが、環境がまだ作れていません。
やります。
近況はこの後書きます。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。