34
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

「Golang」 ×「Gorm」でシンプルに「Mysql」を操作する

Last updated at Posted at 2020-05-31

REST APIサーバから受け取ったリクエストパラメータの内容を元にリレーショナルデータベース(Mysql)を更新する」という流れで、説明を進めています。

以下の記事の続編という形で投稿を行っておりますので、仕様などの前提条件はこちらで確認していただけるとありがたいです。

「Golang」 ×「gorilla/mux router」でシンプルなREST APIサーバーを開発する

データベース(Mysql)準備

コードを実装する前に、Mysql上でデータベースの作成を行います。
Mysql自体導入していない場合は、インストールが必要です。詳細は他のサイトで解説されているのでここでは割愛します。

# ルートユーザーでログイン
mysql -uroot

# mysql shell上
CREATE DATABASE { データベース名 };

# ログインユーザー作成
GRANT ALL PRIVILEGES ON { データベース名 }.* TO 'username'@'localhost' IDENTIFIED BY 'password';

# 一度mysqlからログアウトして、再度作成したユーザーでログイン
mysql -u username -p

# 念の為、データベースが作成されているか確認
SHOW DATABASES;

コード実装

バージョン情報

ORMマッピング

SQL文でデータベースを操作することも可能ですが、今回は「コードを簡素化する」・「単純なDB操作のみ行う」ためにORMでマッピングします。
データベースの複雑な操作が必要な場合はSQLをそのまま書いたほうが、メンテナンスしやすいと思います。
Gormを採用した理由としては「スター数が多い」「ドキュメントが充実している」からです。

root上でgoモジュールインストール
go get github.com/jinzhu/gorm

フォルダ構成

フォルダ構成
merchandise_control_system
├── config
│   └── config.go
├── controllers
│   └── webserver.go
├── models
│   └── middleware.go
├── config.ini
├── go.mod
├── go.sum
└── main.go

サーバポート情報・データベース接続情報をconfigに記載する

APIサーバのポート番号、DBの接続情報は一箇所にまとめ読み込むようにすることで可読性が向上します。

root上でgoモジュールインストール
go get gopkg.in/ini.v1
config.ini
[db]
db_driver_name = mysql
db_name = { データベース名 }
db_user_name = { データベースユーザー名 }
db_user_password = { データベースパスワード }
db_host = 127.0.0.1
db_port = 3306

[api]
server_port = 8080
config/config.go
package config

import (
	"gopkg.in/ini.v1"
	"log"
	"os"
)

type ConfigList struct {
	DbDriverName   string
	DbName         string
	DbUserName     string
	DbUserPassword string
	DbHost         string
	DbPort         string
	ServerPort     int
}

var Config ConfigList

func init() {
	cfg, err := ini.Load("config.ini")
	if err != nil {
		log.Printf("Failed to read file: %v", err)
		os.Exit(1)
	}

	Config = ConfigList{
		DbDriverName:   cfg.Section("db").Key("db_driver_name").String(),
		DbName:         cfg.Section("db").Key("db_name").String(),
		DbUserName:     cfg.Section("db").Key("db_user_name").String(),
		DbUserPassword: cfg.Section("db").Key("db_user_password").String(),
		DbHost:         cfg.Section("db").Key("db_host").String(),
		DbPort:         cfg.Section("db").Key("db_port").String(),
		ServerPort:     cfg.Section("api").Key("server_port").MustInt(),
	}
}

コード全体

イメージとしては、main.goからコントローラを呼び出し、APIサーバを起動。
APIリクエストが行われると、コントローラ内からモデルが呼び出され、データベースの値を参照・更新。
最終的にレスポンスが返されるといった流れになります。

main.go
package main

import (
	"merchandise_control_system/controllers"
)

func main() {
	controllers.StartWebServer()
}
controllers/webserver.go
package controllers

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"merchandise_control_system/config"
	"merchandise_control_system/models"
	"net/http"
	"strconv"
)

type DeleteResponse struct {
	Id string `json:"id"`
}

func rootPage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome to the Go Api Server")
	fmt.Println("Root endpoint is hooked!")
}

func fetchAllItems(w http.ResponseWriter, r *http.Request) {
	var items []models.Item
	// modelの呼び出し
	models.GetAllItems(&items)
	responseBody, err := json.Marshal(items)
	if err != nil {
		log.Fatal(err)
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseBody)
}

func fetchSingleItem(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]

	var item models.Item
	// modelの呼び出し
	models.GetSingleItem(&item, id)
	responseBody, err := json.Marshal(item)
	if err != nil {
		log.Fatal(err)
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseBody)
}

func createItem(w http.ResponseWriter, r *http.Request) {
	reqBody, _ := ioutil.ReadAll(r.Body)

	var item models.Item
	if err := json.Unmarshal(reqBody, &item); err != nil {
		log.Fatal(err)
	}
	// modelの呼び出し
	models.InsertItem(&item)
	responseBody, err := json.Marshal(item)
	if err != nil {
		log.Fatal(err)
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseBody)
}

func deleteItem(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]

	// modelの呼び出し
	models.DeleteItem(id)
	responseBody, err := json.Marshal(DeleteResponse{Id: id})
	if err != nil {
		log.Fatal(err)
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseBody)
}

func updateItem(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]
	reqBody, _ := ioutil.ReadAll(r.Body)

	var updateItem models.Item
	if err := json.Unmarshal(reqBody, &updateItem); err != nil {
		log.Fatal(err)
	}
	// modelの呼び出し
	models.UpdateItem(&updateItem, id)
	convertUintId, _ := strconv.ParseUint(id, 10, 64)
	updateItem.Model.ID = uint(convertUintId)
	responseBody, err := json.Marshal(updateItem)
	if err != nil {
		log.Fatal(err)
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseBody)
}

func StartWebServer() error {
	fmt.Println("Rest API with Mux Routers")
	router := mux.NewRouter().StrictSlash(true)

	router.HandleFunc("/", rootPage)
	router.HandleFunc("/items", fetchAllItems).Methods("GET")
	router.HandleFunc("/item/{id}", fetchSingleItem).Methods("GET")

	router.HandleFunc("/item", createItem).Methods("POST")
	router.HandleFunc("/item/{id}", deleteItem).Methods("DELETE")
	router.HandleFunc("/item/{id}", updateItem).Methods("PUT")

	return http.ListenAndServe(fmt.Sprintf(":%d", config.Config.ServerPort), router)
}

models/middleware.go
package models

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"log"
	"merchandise_control_system/config"
	"time"
)

type Model struct {
	ID        uint        `gorm:"primary_key" json:"id"`
	CreatedAt *time.Time  `json:"created_at"`
	UpdatedAt *time.Time  `json:"updated_at"`
	DeletedAt *time.Time  `json:"deleted_at"`
}

type Item struct {
	Model
	JanCode      string     `gorm:"size:255" json:"jan_code,omitempty"`
	ItemName     string     `gorm:"size:255" json:"item_name,omitempty"`
	Price        int        `json:"price,omitempty"`
	CategoryId   int        `json:"category_id,omitempty"`
	SeriesId     int        `json:"series_id,omitempty"`
	Stock        int        `json:"stock,omitempty"`
	Discontinued bool       `json:"discontinued"`
	ReleaseDate  *time.Time `json:"release_date,omitempty"`
}

var Db *gorm.DB

func GetAllItems(items *[]Item) {
	Db.Find(&items)
}

func GetSingleItem(item *Item, key string) {
	Db.First(&item, key)
}

func InsertItem(item *Item) {
	Db.NewRecord(item)
	Db.Create(&item)
}

func DeleteItem(key string) {
	Db.Where("id = ?", key).Delete(&Item{})
}

func UpdateItem(item *Item, key string) {
	Db.Model(&item).Where("id = ?", key).Updates(
		map[string]interface{}{
			"jan_code":     item.JanCode,
			"item_name":    item.ItemName,
			"price":        item.Price,
			"category_id":  item.CategoryId,
			"series_id":    item.SeriesId,
			"stock":        item.Stock,
			"discontinued": item.Discontinued,
			"release_date": item.ReleaseDate,
		})
}

// データベースの初期化
func init() {
	var err error
	dbConnectInfo := fmt.Sprintf(
		`%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local`,
		config.Config.DbUserName,
		config.Config.DbUserPassword,
		config.Config.DbHost,
		config.Config.DbPort,
		config.Config.DbName,
	)

	// configから読み込んだ情報を元に、データベースに接続します
	Db, err = gorm.Open(config.Config.DbDriverName, dbConnectInfo)
	if err != nil {
		log.Fatalln(err)
	} else {
		fmt.Println("Successfully connect database..")
	}

	// 接続したデータベースにitemsテーブルを作成します
	Db.Set("gorm:table_options", "ENGINE = InnoDB").AutoMigrate(&Item{})
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("Successfully created table..")
	}
}

デモ

APIサーバを起動します。と同時にデータベース接続、テーブル作成が行われます。

go run main.go

Postmanからリクエストを送ってみます。(curlコマンドでもOKです)。

【POST】 /item

最初はデータベースが空のため、データを作成します。

リクエストパラメータ
{
    "jan_code": "32739028488888",
    "item_name": "item_1",
    "price": 1000,
    "category_id": 1,
    "series_id": 1,
    "stock": 1000,
    "discontinued": true,
    "release_date": "2020-05-31T07:00:00.660666+09:00"
}
レスポンスパラメータ
{
    "id": 1,
    "created_at": "2020-05-31T13:46:09.932124+09:00",
    "updated_at": "2020-05-31T13:46:09.932124+09:00",
    "deleted_at": null,
    "jan_code": "32739028488888",
    "item_name": "item_1",
    "price": 1000,
    "category_id": 1,
    "series_id": 1,
    "stock": 1000,
    "discontinued": true,
    "release_date": "2020-05-31T07:00:00.660666+09:00"
}
DB(Mysql)
select * from items;

+----+---------------------+---------------------+------------+----------------+-----------+-------+-------------+-----------+-------+--------------+---------------------+
| id | created_at          | updated_at          | deleted_at | jan_code       | item_name | price | category_id | series_id | stock | discontinued | release_date        |
+----+---------------------+---------------------+------------+----------------+-----------+-------+-------------+-----------+-------+--------------+---------------------+
|  1 | 2020-05-31 13:46:10 | 2020-05-31 13:46:10 | NULL       | 32739028488888 | item_1    |  1000 |           1 |         1 |  1000 |            1 | 2020-05-31 07:00:01 |
+----+---------------------+---------------------+------------+----------------+-----------+-------+-------------+-----------+-------+--------------+---------------------+
1 row in set (0.00 sec)

【GET】 /item/1

レスポンスパラメータ
{
    "id": 1,
    "created_at": "2020-05-31T13:46:10+09:00",
    "updated_at": "2020-05-31T13:46:10+09:00",
    "deleted_at": null,
    "jan_code": "32739028488888",
    "item_name": "item_1",
    "price": 1000,
    "category_id": 1,
    "series_id": 1,
    "stock": 1000,
    "discontinued": true,
    "release_date": "2020-05-31T07:00:01+09:00"
}

【GET】 /items

レスポンスパラメータ
[
    {
        "id": 1,
        "created_at": "2020-05-31T13:46:10+09:00",
        "updated_at": "2020-05-31T13:46:10+09:00",
        "deleted_at": null,
        "jan_code": "32739028488888",
        "item_name": "item_1",
        "price": 1000,
        "category_id": 1,
        "series_id": 1,
        "stock": 1000,
        "discontinued": true,
        "release_date": "2020-05-31T07:00:01+09:00"
    }
]

【PUT】 /item/1

リクエストパラメータ
{
    "jan_code": "3273902878656",
    "item_name": "item_1_update",
    "price": 12000,
    "category_id": 1,
    "series_id": 1,
    "stock": 10,
    "discontinued": false,
    "release_date": "2020-05-30T10:30:19.978603+09:00"
}
レスポンスパラメータ
{
    "id": 1,
    "created_at": null,
    "updated_at": "2020-05-31T14:26:04.165559+09:00",
    "deleted_at": null,
    "jan_code": "3273902878656",
    "item_name": "item_1_update",
    "price": 12000,
    "category_id": 1,
    "series_id": 1,
    "stock": 10,
    "discontinued": false,
    "release_date": "2020-05-30T10:30:19.978603+09:00"
}
DB(Mysql)
select * from items;

+----+---------------------+---------------------+------------+---------------+---------------+-------+-------------+-----------+-------+--------------+---------------------+
| id | created_at          | updated_at          | deleted_at | jan_code      | item_name     | price | category_id | series_id | stock | discontinued | release_date        |
+----+---------------------+---------------------+------------+---------------+---------------+-------+-------------+-----------+-------+--------------+---------------------+
|  1 | 2020-05-31 14:25:15 | 2020-05-31 14:26:04 | NULL       | 3273902878656 | item_1_update | 12000 |           1 |         1 |    10 |            0 | 2020-05-30 10:30:20 |
+----+---------------------+---------------------+------------+---------------+---------------+-------+-------------+-----------+-------+--------------+---------------------+
1 row in set (0.00 sec)

【DELETE】 /item/1

レスポンスパラメータ
{
    "id": "1"
}

まとめ

Gormを使うと、比較的簡単にデータベース接続、テーブル作成、テーブル操作を行うことができました。
また、ORMの特徴として、DBを他のDB(例えばPostgreSQL)に変更したとしてもコードの変更が少なくて済むのもメリットかと思います。

細かいコードメンテナンスは必要ですが、一旦最低限の「APIサーバ起動」と「DB接続」が可能になりました。
今後こちらのサーバサイドコードを成長させながら、「ビュー」部分をReactNativeで実装していきたいと思います。

34
24
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
34
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?