GORMを使用してMySQLに接続し、データベースの作成・削除とCRUD機能を実装する

# 概要
[GORM](https://gorm.io/ja_JP/)を初めて使用したので、基本的な使用方法をアウトプットのため投稿する。

■ Go version
go1.15.7
■ MySQL version
5.7.37

## GORMを導入する
以下のコードで必要なパッケージをインストールすることができる。
```
go mod init [任意の値]
go get -u gorm.io/gorm
go get -u github.com/go-sql-driver/mysql
```

# テーブルの作成
以下のコードでテーブルの作成・削除が可能であった。

### GOのコード
```migrate.go
package main

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

type Data1 struct {
    gorm.Model
	Title    string
	Content  string
}

func main() {
    dsn := "[ユーザー名]:[パスワード]@tcp(127.0.0.1:3306)/[スキーマ―名]?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
		panic("failed to connect database")
	} else {
        up01(dsn, db)
        down01(dsn, db);
	}
}

func up01(dsn string, db *gorm.DB) {
    db.AutoMigrate(Data1{})
}
func down01(dsn string, db *gorm.DB) {
    db.Migrator().DropTable(Data1{})
}
```

### 作成されるテーブル
```sql
CREATE TABLE `data1` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    `created_at` datetime(3) DEFAULT NULL,
    `updated_at` datetime(3) DEFAULT NULL,
    `deleted_at` datetime(3) DEFAULT NULL,
    `title` longtext,
    `content` longtext,
    PRIMARY KEY (`id`),
    KEY `idx_data1_deleted_at` (`deleted_at`)
) ENGINE = InnoDB DEFAULT CHARSET = latin1
```

## 解説
1つ1つのコードの解説を記載する。
### パッケージ

```go
gorm.io/driver/mysql
gorm.io/gorm
```
GORMでMySQLを使用するパッケージを導入している。
以下にGItHubのリンクを記載している。
https://github.com/go-gorm/mysql

### 構造体
```go
type Data1 struct {
    gorm.Model
	Title    string
	Content  string
}
```

テーブルの生成・削除に使用する構造体を宣言している。
`gorm.Model`の記述があることで`ID, CreatedAt, UpdatedAt, DeletedAt`カラムが構造体に記載せずともテーブルに反映されるようになる。
また、構造体名が小文字だと初期化時にフィールドには存在しているが、DB作成時にカラムが生成されていないので大文字にする必要がある。

### データベースに接続
```go
dsn := "[ユーザー名]:[パスワード]@tcp(127.0.0.1:3306)/[スキーマ―名]?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
```
[こちら](https://gorm.io/ja_JP/docs/connecting_to_the_database.html)のページに詳細が記載されているが、上記コードでデータベースに接続可能であった。
ユーザー名、IPアドレス、ポート番号、charset等は各自の環境に合わせて変更する必要がある。

### 関数呼び出し
```go
if err != nil {
	panic("failed to connect database")
} else {
	up01(dsn, db)
	down01(dsn, db);
}
```
データベースに接続できた場合に関数を呼び出している。
現状だと2つの関数が実行されるので、テーブルを作成したい場合は`down01関数`の呼び出しをコメントアウトし、テーブルを削除したい場合は`up01関数`の呼び出しをコメントアウトする。

### テーブルの作成・削除を行う関数
```go
func up01(dsn string, db *gorm.DB) {
    db.AutoMigrate(Data1{})
}
func down01(dsn string, db *gorm.DB) {
    db.Migrator().DropTable(Data1{})
}
```

上記内容でテーブルの作成・削除が可能である。

# CRUD
以下のコードでCRUD機能を実装した。
解説を記載していく。
```go
package main

import (
	"fmt"
    "gorm.io/gorm"
    "gorm.io/driver/mysql"
)
// 構造体名を大文字にしないと以下のエラーになる
// 「struct field title has json tag but is not exportedstructtag」
type Data1 struct {
    gorm.Model
	Title    string `json:"title"`
	Content  string `json:"content"`
}

func main() {
    dsn := "[ユーザー名]:[パスワード]@tcp(127.0.0.1:3306)/[スキーマ―名]?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
		panic("failed to connect database")
	}

    var data1 Data1
    var data1_arr []Data1

    // Create
    db.Debug().Create(&Data1{Title: "title1", Content: "content1"})

    // Create Multi
    var multi_create = []Data1{{Title: "title2", Content: "content2"}, {Title: "title3", Content: "content3"}, {Title: "title4", Content: "content3"}}
    db.Debug().Create(&multi_create)

    // Read
    db.Debug().First(&data1, 1)
    fmt.Println(data1)
    read_json, err := json.Marshal(data1)
    fmt.Println(string(read_json))

    // Read Multi
    db.Debug().Find(&data1_arr)
    fmt.Println(data1_arr)
    read_multi_json, err := json.Marshal(data1_arr)
    fmt.Println(string(read_multi_json))

    // Update
    db.Debug().Model(&Data1{}).Where("id = ?", 1).Update("Title", "titleUpdate")
    // Update Multi
    db.Debug().Model(&data1).Where("id = ?", 1).Updates(Data1{Title: "titleMultUpdate", Content: "ContentMultUpdate"})

    // Delete
    db.Debug().Delete(&data1, 1)
}
```

※`Debug`メソッドはターミナルにSQL文を出力するために使用している。

## Create
Createメソッドを使用する。
宣言した構造体をポインタを用いて初期化して、挿入したい値を記載する。
`&`を使用せずに実行すると以下のエラーになる。
```
panic: reflect: reflect.Value.Set using unaddressable value
```

### 1レコードの挿入

```go
db.Create(&Data1{Title: "title1", Content: "content1"})
```

### 発行されるSQL
```sql
INSERT INTO `data1` (`created_at`,`updated_at`,`deleted_at`,`title`,`content`) VALUES ('2022-08-06 12:52:12.414','2022-08-06 12:52:12.414',NULL,'title1','content1')
```
### 複数レコードの挿入
構造体初期化時に配列を使用することで複数レコードの挿入が可能。
```go
var multi_create = []Data1{{Title: "title2", Content: "content2"}, {Title: "title3", Content: "content3"}, {Title: "title4", Content: "content3"}}
db.Debug().Create(&multi_create)
```

```sql
INSERT INTO `data1` (`created_at`,`updated_at`,`deleted_at`,`title`,`content`) VALUES ('2022-08-06 12:52:12.417','2022-08-06 12:52:12.417',NULL,'title2','content2'),('2022-08-06 12:52:12.417','2022-08-06 12:52:12.417',NULL,'title3','content3'),('2022-08-06 12:52:12.417','2022-08-06 12:52:12.417',NULL,'title4','content3')
```

### その後のデーターベース
データーベースを確認すると意図した内容で登録されていることが確認できる。
![スクリーンショット 2022-08-06 12.58.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/d31b7a39-cdad-cfb3-aba2-03346b3f03fc.png)


## Read
### WHERE句の使用
```go
db.Debug().First(&data1, 1)
```

```sql
SELECT * FROM `data1` WHERE `data1`.`id` = 1 AND `data1`.`deleted_at` IS NULL ORDER BY `data1`.`id` LIMIT 1
```
取得できた値
```json
{{1 2022-08-06 12:52:12.414 +0900 JST 2022-08-06 12:52:12.414 +0900 JST {0001-01-01 00:00:00 +0000 UTC false}} title1 content1}
// JSON変換時
{"ID":1,"CreatedAt":"2022-08-06T12:52:12.414+09:00","UpdatedAt":"2022-08-06T12:52:12.414+09:00","DeletedAt":null,"title":"title1","content":"content1"}
```
### 全レコードの取得
Findメソッド内の関数が配列でないと1レコードしか値を取得できないため、配列を使用して初期化している`data1_arr`変数（構造体）を使用している。
```go
db.Debug().Find(&data1_arr)
```

```sql
SELECT * FROM `data1` WHERE `data1`.`deleted_at` IS NULL
```

```json
[{{1 2022-08-06 12:52:12.414 +0900 JST 2022-08-06 12:52:12.414 +0900 JST {0001-01-01 00:00:00 +0000 UTC false}} title1 content1} {{2 2022-08-06 12:52:12.417 +0900 JST 2022-08-06 12:52:12.417 +0900 JST {0001-01-01 00:00:00 +0000 UTC false}} title2 content2} {{3 2022-08-06 12:52:12.417 +0900 JST 2022-08-06 12:52:12.417 +0900 JST {0001-01-01 00:00:00 +0000 UTC false}} title3 content3} {{4 2022-08-06 12:52:12.417 +0900 JST 2022-08-06 12:52:12.417 +0900 JST {0001-01-01 00:00:00 +0000 UTC false}} title4 content3}]
// JSON変換時
[{"ID":1,"CreatedAt":"2022-08-06T12:52:12.414+09:00","UpdatedAt":"2022-08-06T12:52:12.414+09:00","DeletedAt":null,"title":"title1","content":"content1"},{"ID":2,"CreatedAt":"2022-08-06T12:52:12.417+09:00","UpdatedAt":"2022-08-06T12:52:12.417+09:00","DeletedAt":null,"title":"title2","content":"content2"},{"ID":3,"CreatedAt":"2022-08-06T12:52:12.417+09:00","UpdatedAt":"2022-08-06T12:52:12.417+09:00","DeletedAt":null,"title":"title3","content":"content3"},{"ID":4,"CreatedAt":"2022-08-06T12:52:12.417+09:00","UpdatedAt":"2022-08-06T12:52:12.417+09:00","DeletedAt":null,"title":"title4","content":"content3"}]
```

## Update
### 単一カラム
```go
db.Model(&Data1{}).Where("id = ?", 1).Update("Title", "titleUpdate")
```

```sql
UPDATE `data1` SET `title`='titleUpdate',`updated_at`='2022-08-06 14:48:57.211' WHERE id = 1 AND `data1`.`deleted_at` IS NULL
```

### 複数カラム
```go
db.Model(&data1).Where("id = ?", 1).Updates(Data1{Title: "titleMultUpdate", Content: "ContentMultUpdate"})
```

```sql
UPDATE `data1` SET `updated_at`='2022-08-06 14:48:57.217',`title`='titleMultUpdate',`content`='ContentMultUpdate' WHERE id = 2 AND `data1`.`deleted_at` IS NULL
```

### 変更後
ID`1`、`2`のレコードのカラムが変更されていることがわかる。
![スクリーンショット 2022-08-06 14.57.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/5cc967be-569e-8754-aab5-14ad2756fe4c.png)

### エラー
[GORMのリファレンス](https://gorm.io/docs/update.html#Block-Global-Updates)に記載されているが、全レコードの更新を行おうとするとエラーが出る。
Update文ではWHERE句を使用するべきである。

```go
db.Model(&data1).Update("title1", "titleUpdate")

// error
WHERE conditions required
```

### Delete
今回は論理削除を取り扱う。
ID1番のレコードを削除する。

```go
db.Debug().Delete(&data1, 1)
```

```sql
UPDATE `data1` SET `deleted_at`='2022-08-06 14:59:27.779' WHERE `data1`.`id` = 1 AND `data1`.`deleted_at` IS NULL
```

###  poiter
ポインタがないとエラーになる
```
panic: reflect: reflect.Value.Set using unaddressable value
```

### 結果
ID`1`のレコードの`deleted_at`カラムに値が挿入されていることが分かる。

![スクリーンショット 2022-08-06 15.00.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/14776b70-ee3f-05c2-105a-606416095ce3.png)