概要
本記事では、Golangにおけるdatabase/sqlライブラリを用いたMySQLデータベースに対する処理例を示す。
処理例では、ユーザーデータを仮定し、エラー処理についても例を示す。
以下のようになテーブルを想定する。
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をインストールする。
go get github.com/go-sql-driver/mysql
処理の流れ
実行する処理は、以下のプログラムの通りである。
1. ユーザ1データの挿入
2. ユーザーデータ取得ミス例(エラー処理を示すため)
3. ユーザ2データ挿入
4. Status1のユーザデータを探索、取得
5. ユーザーデータ取得(4で取得したがGETの例を示すため)
6. ユーザーデータの更新
7. ユーザーデータの削除
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の設定を行うほうが良い。
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を実行する場合は、アドレスを渡す必要があることに注意する。
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/を参考にしてみてください。