LoginSignup
14
12

More than 3 years have passed since last update.

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

Posted at

概要

本記事では、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. ユーザーデータの削除

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の設定を行うほうが良い。

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を実行する場合は、アドレスを渡す必要があることに注意する。

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/を参考にしてみてください。

14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12