GORMでMYSQLに接続してCRUD操作する

#はじめに
転職にあたりgo言語を使用することになったので勉強して２日くらいです。
書き方なんとなくだけど分かったので、文字読むより手を動かそうと思い、いきなりDB接続しようとしたらめちゃ時間かかった（当たり前）のでその備忘録です。
間違いや、よろしくない書き方などあったらぜひぜひコメントで教えていただけると嬉しいです。
とにかくDBつないで動かしてみることが目的なので実用的ではないかもしれません。

#環境
GOやMYSQLの環境構築は割愛。調べればたくさん出てきます。

OS: windows
go version: go1.17.1 windows/amd64
mysql version: Ver 8.0.26 for Win64 on x86_64 (MySQL Community Server - GPL)

#GORMとは
GO言語のORMライブラリです。公式がとても分かりやすい

https://gorm.io/ja_JP/

##ORMとは
`Object-relational mapping`の略です。
名前の通り、オブジェクト（オブジェクト指向におけるオブジェクト）と関係（RDB）とのマッピングを行うものです。
超簡単に言うと、SQLを直接書かなくていいやつ。
詳しく知りたい方は調べてみてください。

#インストール

```.sh
#gorm
$ go get -u github.com/jinzhu/gorm

# mysql
$ go get -u github.com/go-sql-driver/mysql
```

#MYSQLに接続
ひとまずCRUDは置いておいて接続を先にしてみます。
以下のように記述しました。go runで走らせてエラーが出なければ接続ができているはずです。

```main.go
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

const (
	DBMS = "mysql"
	DB_USER = "ユーザー名"
	DB_PASS = "パスワード"
	DB_PROTOCOL = "tcp(127.0.0.1:3306)"
	DB_NAME = "DB名"
)

func connectGorm() *gorm.DB {
	connectTemplate := "%s:%s@%s/%s?parseTime=True"
	connect := fmt.Sprintf(connectTemplate, DB_USER, DB_PASS, DB_PROTOCOL, DB_NAME)
	db, err := gorm.Open(DBMS, connect)

	if err != nil {
		log.Println(err.Error())
	}

	return db
}

func main() {
	db := connectGorm()
	defer db.Close()
}

```

##connectGorm()について捕捉
接続の設定は以下のように書きます

```main.go

    db, err := gorm.Open("mysql", "ユーザー名:パスワード@/DB名")

    if err != nil {
        panic(err.Error())
    }
    return db
```

ユーザー名等は一括管理したいなと思い、constで定義してフォーマットさせました。ymlファイル的なの作りたいけどディレクトリ構造とかわからないのでそれはおいおい。。。

MySQLだと文字コードの問題で`?parseTime=true`を末尾につける必要があるらしいのでつけました。

#テーブル定義
テーブルを定義してマイグレーションしたら作ってくれるらしい。
構造体でテーブルの構造を定義します。たぶんentity的な感じ。プロジェクト情報を持つテーブルを作ってみます。

以下を追加

```main.go
type Projects struct {
	gorm.Model
	Name    string `gorm:"size:50"`
	Detail  string `gorm:"size:255"`
}

func main() {
 ～略～
	db.Set("gorm:table_options", "ENGINE = noDB").AutoMigrate(&Projects{})
```

**カラム名にあたる部分はパスカルケースで書かないと認識してくれなかった**ので注意が必要です。DBではスネークケースで登録されます。（例：UserId → user_id）

`db.Set("gorm:table_options", "ENGINE = noDB").AutoMigrate(&Projects{})`でテーブルが存在しない時に対象のテーブルを作成してくれます。

実行後にDBを確認するとテーブルが作られています。

```.sh
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
| deleted_at | datetime     | YES  | MUL | NULL    |                |
| name       | varchar(50)  | YES  |     | NULL    |                |
| detail     | varchar(255) | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
```

以下のカラムは自動で追加されました。
`id`
`created_at`
`updated_at`
`deleted_at`


これは`gorm.Model`を使用したためです。`Model`の中身を見てみると自動追加されている４つの項目が書かれていました。

```model.go
package gorm

import "time"

// Model base model definition, including fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, which could be embedded in your models
//    type User struct {
//      gorm.Model
//    }
type Model struct {
	ID        uint `gorm:"primary_key"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time `sql:"index"`
}
```

#CRUD操作
テーブルができたのでCRUDの操作をしてみましょう。

##CREATE

`insert`の関数を追加します。

```main.go
func insert(projects []Projects, db *gorm.DB) {
	for _, project := range projects {
		db.Create(&project)
	}
}
```
実際にinsertするときは以下のように使用します。

```main.go
	project := Projects{Name: "プロジェクト名", Detail: "詳細"}
	insertProjects := []Projects{project}
	insert(insertProjects, db)
```

##READ
###全件取得

```main.go
func findAll(db *gorm.DB) []Projects {
	var allProjects []Projects
	db.Find(&allProjects)
	return allProjects
}
```

###指定したIDのデータを取得

```main.go
func findByID(db *gorm.DB, id int) Projects {
	var project Projects
	db.First(&project, id)
	return project
}
```
引数でIDを渡して検索しています。
存在しない場合は初期値が返ってきます。

###指定した名前のデータを取得

```main.go
func findByName(db *gorm.DB, name string) []Projects {
	var project []Projects
	db.Where("name = ?", name).Find(&project)
	return project
}
```
IDと同じような書き方です。`where`と`find`を合わせて使っています。
ヒットしたものをすべて返します。

##UPDATE
###IDをキーに更新

```main.go
func updateWhereID(id int, newName string, db *gorm.DB) {
	var project Projects
	db.Model(&project).Where("id = ?", id).Update("name", newName)
}
```
##DELETE

###IDをキーに削除
```main.go
func deleteByID(id int, db *gorm.DB) {
	var project Projects
	db.Where("id = ?", id).Delete(project)
}
```

####捕捉
`gorm.Model`を使用していて`deleted_at`カラムが存在する場合は、`deleted_at`に時間が追加されて取得されなくなります。つまり**倫理削除（UPDATE）**されます。

`deleted_at`カラムがない場合は物理削除されます。

#参考サイト
https://gorm.io/ja_JP/docs/create.html
[gormを使ってローカル環境のMySQLとCRUD操作](https://qiita.com/gatapon/items/e788c571c2cc7e8abb60)
[Gormを触ってみた(MySQL)](https://qiita.com/kai993/items/389cdec6a01bd527525b)

#さいごに
２日目にしてはまあできたほうなんじゃないでしょうか。
とにかく動くことだけを目的に作ったのでもっとMVCライクな感じに書きたいです。いい記事がなかなか見つからず、古い記事を参考に、公式を見ながら直しつつ書いたのであまりにひどかったらコメントください。。
次回は整理してファイル分けしたり、GIN使ってJSONで返すようにしたりしてみたいです。

さいごまで読んでいただきありがとうございます！