【Golang】 mysql データベース処理

# 概要

本記事では、Golangにおけるdatabase/sqlライブラリを用いたMySQLデータベースに対する処理例を示す。
処理例では、ユーザーデータを仮定し、エラー処理についても例を示す。

以下のようになテーブルを想定する。

```sql

CREATE TABLE user_db.users (
  id INT NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(45) NULL,
  last_name VARCHAR(45) NULL,
  email VARCHAR(45) NOT NULL,
	data_created DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
	status TINYINT(1) NOT NULL,
  PRIMARY KEY (id),
	UNIQUE INDEX email_UNIQUE (email ASC));
```
# pkgのインストール

mysqlのドライバーpkgをインストールする。

```bash
go get github.com/go-sql-driver/mysql
```


# 処理の流れ

実行する処理は、以下のプログラムの通りである。
1. ユーザ1データの挿入
2. ユーザーデータ取得ミス例(エラー処理を示すため)
3. ユーザ2データ挿入
4. Status1のユーザデータを探索、取得
5. ユーザーデータ取得(4で取得したがGETの例を示すため）
6. ユーザーデータの更新
7. ユーザーデータの削除


```go:main.go
package main

import (
	"fmt"
	"log"

	"github.com/k-washi/golang-cookbook/database/user_db/method"
)

func main() {

	//ユーザデータの挿入
	user := &method.User{ID: 1, FirstName: "Trou", LastName: "Tanaka", Email: "123s456@test.com", Status: 1}
	if err := user.Save(); err != nil {
		log.Println(err)
		//2020/01/23 03:15:08 email already exist
	}
	log.Println(user)
	//2020/01/23 04:06:32 &{1 Trou Tanaka 123s456@test.com <nil>}

	//ユーザーデータの取得(IDミス)
	user = &method.User{ID: 3}
	if err := user.Get(); err != nil {
		log.Println(err)
		//2020/01/23 03:26:24 user not found
	}
	log.Println(user)

	//ユーザデータ挿入
	user = &method.User{ID: 2, FirstName: "k", LastName: "washi", Email: "654321@test.com", Status: 1}
	if err := user.Save(); err != nil {
		log.Println(err)
	}

	//ユーザデータのStatus:1をFind
	res, err := user.FindByStatus(1)
	if err != nil {
		log.Println(err)
	}
	for i, u := range res {
		fmt.Println("user :", i, u)
		//user : 0 {37 Trou Tanaka 123s456@test.com 2020-01-23 05:07:35 +0000 UTC 1}

		//ユーザデータの取得(IDを使用するため、ループ内で処理)
		user = &method.User{ID: u.ID}
		if err := user.Get(); err != nil {
			log.Println(err)
		}
		log.Println(user)
		//2020/01/23 03:03:52 &{1 Trou Tanaka 123s456@test.com 2020-01-23 02:29:56 +0000 UTC}

		//ユーザデータ更新
		user.FirstName = "Taro2"
		user.LastName = "Tanaka2"
		user.Email = "123s456@test.com"
		user.Status = 1
		if err := user.Update(); err != nil {
			log.Println(err)
		}
		log.Println(user)
		//2020/01/23 03:54:51 &{1 Taro2 Tanaka2 123s456@test.com 2020-01-23 02:29:56 +0000 UTC}

		//ユーザデータの削除
		user = &method.User{ID: u.ID}
		if err := user.Delete(); err != nil {
			log.Println(err)
		}
		log.Println("Success Delete")

	}

}
```
# データベースの初期設定

sql.OpenでDBと接続を確立し、接続を確立している。
 _ "github.com/go-sql-driver/mysql"をmysqlを扱うため、インポートしている。
設定は,user:password@tcp(host:port)/dbnameの形式の引数をとり、parseTime=trueは、時刻を扱うために設定している。

Client, err := sql.Open()とせず、予め、Client,errを定義し、client, err = sql.Open()としている。前者では、もしClientを関数外で定義している場合、nilになるため。

また、コメントアウトしているが、実際は、環境変数を用いて、DBの設定を行うほうが良い。


```go:user_db/user_db.go
package user_db

import (
	"database/sql"
	"fmt"
	"log"

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

var (
	//Client user_id database
	Client *sql.DB
	err    error
)


/*
//実際は、環境変数を読み込んで、DBの設定を行う。
//export mysql_user_name="root"
const (
	mysql_user_naem: "mysql_user_name"
)

var userName := os.Getenv(mysql_user_naem)
*/

func init() {
	//user:password@tcp(host:port)/dbname
	dataSourceName := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true",
		"root", "", "127.0.0.1:3306", "user_db",
	)

	log.Println(fmt.Sprintf("about to connect to %s", dataSourceName))
	//open db by mysql driver and data source name.
	//https://github.com/go-sql-driver/mysql/issues/150 (Issue)
	Client, err = sql.Open("mysql", dataSourceName)
	if err != nil {
		panic(err)
	}

	if err = Client.Ping(); err != nil {
		panic(err)
	}
	log.Println("database successfully configured")
}
```

# データベース処理

GET, POSTなどのメソッドを定義している。
ここで定義しているUser 構造体が本記事で扱っているユーザデータである。const内には、ここで扱うSQLを定義している。

エラーはparseErrorとして定義している。mysqlに関するエラーは個別に取り出せ、switcで場合できることを例で示している。1062は、Uniqueに対するエラーである。

GET, SELECTの際、*.Scanを実行する場合は、アドレスを渡す必要があることに注意する。


```go:method/method.go
package method

import (
	"fmt"
	"log"
	"strings"
	"time"

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

	"github.com/k-washi/golang-cookbook/database/user_db/user_db"
)

const (
	queryInsertUser         = "INSERT INTO users(first_name, last_name, email, status) VALUES(?, ?, ?, ?);"
	queryGetUser            = "SELECT id, first_name, last_name, email, data_created, status FROM users WHERE id=?;"
	queryUpdateUser         = "UPDATE users SET first_name=?, last_name=?, email=?, status=? WHERE id=?;"
	queryDeleteUser         = "DELETE FROM users WHERE id=?;"
	querySelectUserByStatus = "SELECT id, first_name, last_name, email, data_created, status FROM users WHERE status=?;"
	errorNoRow              = "no rows in result set"
)

//User info
type User struct {
	ID        int64
	FirstName string
	LastName  string
	Email     string
	Date      *time.Time
	Status    int
}

var (
//UserDB = make(map[int64]*User)
)

func (user *User) Get() error {
	stmt, err := user_db.Client.Prepare(queryGetUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	result := stmt.QueryRow(user.ID)
	if getErr := result.Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &user.Date, &user.Status); getErr != nil {

		return parseError(getErr)
	}
	return nil
}

func (user *User) Save() error {
	stmt, err := user_db.Client.Prepare(queryInsertUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	insertResult, saveErr := stmt.Exec(user.FirstName, user.LastName, user.Email, user.Status)
	if saveErr != nil {
		return parseError(saveErr)
	}

	userID, err := insertResult.LastInsertId()
	if err != nil {
		return err
	}
	user.ID = userID

	log.Println("Save user data")

	return nil
}

func (user *User) Update() error {
	stmt, err := user_db.Client.Prepare(queryUpdateUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	if _, updErr := stmt.Exec(user.FirstName, user.LastName, user.Email, user.Status, user.ID); updErr != nil {
		return parseError(updErr)
	}
	return nil
}

func (user *User) Delete() error {
	stmt, err := user_db.Client.Prepare(queryDeleteUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	if _, delErr := stmt.Exec(user.ID); delErr != nil {
		return parseError(delErr)
	}

	return nil
}

func (user *User) FindByStatus(status int) ([]User, error) {
	stmt, err := user_db.Client.Prepare(querySelectUserByStatus)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()

	rows, err := stmt.Query(status)
	if err != nil {
		return nil, parseError(err)
	}
	defer rows.Close()

	res := make([]User, 0)
	for rows.Next() {
		var user User
		if err := rows.Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &user.Date, &user.Status); err != nil {
			return nil, parseError(err)
		}
		res = append(res, user)
	}

	return res, nil
}

func parseError(err error) error {
	sqlErr, ok := err.(*mysql.MySQLError)
	if !ok {
		if strings.Contains(err.Error(), errorNoRow) {
			return fmt.Errorf("user not found")
		}
		return err
	}
	//以下,sql依存のエラー
	switch sqlErr.Number {
	case 1062:
		//email_UNIQUE error
		return fmt.Errorf("email already exist")
	}
	return err
}

```

# まとめ

GolangによるMySQLデータベースに対する処理の例を示しました。
コードは、[k-washi/golang-cookbook/database/user_db/](https://github.com/k-washi/golang-cookbook/tree/master/database/user_db)を参考にしてみてください。