💨

Gorm(golang用ORマッパー)を使う

2022/03/15に公開
【環境】
MacBook Air (M1, 2020)
OS: MacOS Big Sur version11.6
Docker Desktop for Mac version4.5.0

MySQLとgolangのsql/databaseでCRUDを実装し、htmlで操作する記事を書きました。
https://zenn.dev/ajapa/articles/03dcf8fd12d086
sql/databaseをgo用ORマッパーのGormに置き換えていきます。
ORマッパー:O(オブジェクト指向)とR(リレーショナルデータベース)の間でデータ形式の相互変換を行う。

database.go

database.go
package utility

import (
	"fmt"
	"os"
	"time"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var Db *gorm.DB

func init() {
	user := os.Getenv("MYSQL_USER")
	pw := os.Getenv("MYSQL_PASSWORD")
	db_name := os.Getenv("MYSQL_DATABASE")
	var path string = fmt.Sprintf("%s:%s@tcp(db:3306)/%s?charset=utf8&parseTime=true", user, pw, db_name)
	dialector := mysql.Open(path)
	var err error
	if Db, err = gorm.Open(dialector); err != nil {
		connect(dialector, 100)
	}
	fmt.Println("db connected!!")
}

func connect(dialector gorm.Dialector, count uint) {
	var err error
	if Db, err = gorm.Open(dialector); err != nil {
		if count > 1 {
			time.Sleep(time.Second * 2)
			count--
			fmt.Printf("retry... count:%v\n", count)
			connect(dialector, count)
			return
		}
		panic(err.Error())
	}
}
  • Gormは2020年からバージョン2が利用可能です。
    "gorm.io/gorm"
    でパッケージをインポートします。
    Gormで使うMySQL用のドライバーは
    "gorm.io/driver/mysql"
    でインポートできます。
    標準パッケージのsql/databaseではブランクインポート(パッケージの前に"_ "を記述し、init関数のみを実行)でしたが、Gormで使うドライバーは通常のインポートとなります。
  • GormのOpen関数でDBを取得する時、Dialectorを渡します。
    DialectorはドライバのOpen関数にMySQL接続情報を渡すことで取得できます。
  • MySQLが立ち上がるまで接続を繰り返す際、sql/databaseではPingを繰り返し実行していましたが、GormではOpen関数を実行した時点で接続を試みます。

article.go

article.go
package article

import (
	"go_blog/internal/utility"
	"html/template"
	"log"
	"net/http"
	"strings"
)

type Article struct {
	Id    int
	Title string
	Body  string
}

var tmpl *template.Template

func init() {
	funcMap := template.FuncMap{
		"nl2br": func(text string) template.HTML {
			return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br />", -1))
		},
	}

	tmpl, _ = template.New("article").Funcs(funcMap).ParseGlob("web/template/*")

	utility.Db.Set("gorm:table_options", "ENGINE = InnoDB").AutoMigrate(Article{})
}

func Index(w http.ResponseWriter, r *http.Request) {
	var allArticles []Article
	utility.Db.Find(&allArticles)
	if err := tmpl.ExecuteTemplate(w, "index.html", allArticles); err != nil {
		log.Fatal(err)
	}
}

func Show(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	var article Article
	utility.Db.First(&article, id)
	tmpl.ExecuteTemplate(w, "show.html", article)
}

func Create(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		tmpl.ExecuteTemplate(w, "create.html", nil)
	} else if r.Method == "POST" {
		title := r.FormValue("title")
		body := r.FormValue("body")
		article := Article{Title: title, Body: body}
		utility.Db.Create(&article)
		http.Redirect(w, r, "/", 301)
	}
}

func Edit(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		id := r.URL.Query().Get("id")
		var article Article
		utility.Db.First(&article, id)
		tmpl.ExecuteTemplate(w, "edit.html", article)
	} else if r.Method == "POST" {
		title := r.FormValue("title")
		body := r.FormValue("body")
		id := r.FormValue("id")

		var article Article
		utility.Db.First(&article, id)
		article.Title = title
		article.Body = body
		utility.Db.Save(&article)
		http.Redirect(w, r, "/", 301)
	}
}

func Delete(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		id := r.URL.Query().Get("id")
		var article Article
		utility.Db.First(&article, id)
		tmpl.ExecuteTemplate(w, "delete.html", article)
	} else if r.Method == "POST" {
		id := r.FormValue("id")
		utility.Db.Delete(&Article{}, id)
		http.Redirect(w, r, "/", 301)
	}
}

CRUDの各処理の記述量がかなり減りました。

データの取得

レコードの取得|GORM
IndexにてFind関数を使い、構造体の形でデータを全件取得しています。
またIdを参照して1件取得する時はFirst関数を使います。

データの作成

レコードの作成|GORM
Create関数に構造体を渡すことでデータを作成できます。

データの更新

レコードの更新|GORM
まずはFirst関数でデータを1件取得し、各カラムを更新、Save関数でデータの更新を行います。

データの削除

レコードの削除|GORM
Delete関数にテーブルと主キー(Id)を指定してデータを削除します。

参考

https://gorm.io/ja_JP/

Discussion