Gormを触ってみた(MySQL)

## 環境
```sh
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.4
BuildVersion:	18E226

$ mysql --version
mysql  Ver 8.0.13 for osx10.14 on x86_64 (Homebrew)
```

## 準備
| 項目       | 内容        |
|:-----------|:------------|
| DB名       | go_sample   |
| ユーザー   | user1       |
| パスワード | Password_01 |

```sql
-- ユーザー追加
mysql> create user 'user1'@'localhost' identified by 'Password_01';
mysql> grant all privileges on `go_sample`.* to user1@localhost;
```

次のテーブルをGo側で作成する。

```sql
mysql> desc go_sample.user;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(52) | YES  |     | NULL    |       |
| age   | int(11)     | YES  |     | NULL    |       |
| sex   | int(11)     | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
```

## Gormとは
- Go言語用のORMライブラリで、開発者に優しい

## インストール
```sh
# gorm
$ go get -u github.com/jinzhu/gorm

# mysqlドライバー入れていない場合
$ go get -u github.com/go-sql-driver/mysql
```

## とりあえず接続してみる

```go
package main

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

const (
	// データベース
	Dialect = "mysql"

	// ユーザー名
	DBUser = "user1"

	// パスワード
	DBPass = "Password_01"

	// プロトコル
	DBProtocol = "tcp(127.0.0.1:3306)"

	// DB名
	DBName = "go_sample"
)

func connectGorm() *gorm.DB {
	connectTemplate := "%s:%s@%s/%s"
	connect := fmt.Sprintf(connectTemplate, DBUser, DBPass, DBProtocol, DBName)
	db, err := gorm.Open(Dialect, connect)

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

	return db
}

func main() {
	db := connectGorm()
	defer db.Close()
}
```
エラーがなければ接続完了。

### テーブル定義
次にテーブル構造を定義する。

```go
type User struct {
	gorm.Model
	Name string `gorm:"size:255"`
	Age  int
	Sex  string `gorm:"size:255"`
}

func (u User) String() string {
	return fmt.Sprintf("%s(%d)", u.Name, u.Age)
}
```

次は自動的にカラムが追加される。
- `id`
- `created_at`
- `updated_at`
- `deleted_at`

```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"`
}
```

`main()`関数の末尾に次のコードを追加する。<br>
テーブルが存在しない時に対象のテーブルを作成してくれる。

```go
db.Set("gorm:table_options", "ENGINE = InnoDB").AutoMigrate(&User{})
```

実行後、`users`テーブルが作成されている。

```sql
mysql> show tables;
+-------------------------+
| Tables_in_go_sample     |
+-------------------------+
| users                   |
+-------------------------+
1 row in set (0.00 sec)
```

テーブル構造

```sql
mysql> desc users;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
| deleted_at | timestamp        | YES  | MUL | NULL    |                |
| name       | varchar(255)     | YES  |     | NULL    |                |
| age        | int(11)          | YES  |     | NULL    |                |
| sex        | varchar(255)     | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
```

## データの操作

### insert
```go
func insert(users []User, db *gorm.DB) {
	for _, user := range users {
		db.NewRecord(user)
		db.Create(&user)
	}
}

func main() {
  ...
  user1 := User{Name: "山田太郎", Age: 25, Sex: "男"}
	user2 := User{Name: "田中花子", Age: 22, Sex: "女"}
	insertUsers := []User{user1, user2}
	insert(insertUsers, db)
}
```

実行後のデータは次。

```sql
mysql> select * from users;
+----+---------------------+---------------------+------------+--------------+------+------+
| id | created_at          | updated_at          | deleted_at | name         | age  | sex  |
+----+---------------------+---------------------+------------+--------------+------+------+
|  1 | 2019-04-05 04:21:02 | 2019-04-05 04:21:02 | NULL       | 山田太郎     |   25 | 男   |
|  2 | 2019-04-05 04:21:02 | 2019-04-05 04:21:02 | NULL       | 田中花子     |   22 | 女   |
+----+---------------------+---------------------+------------+--------------+------+------+
2 rows in set (0.00 sec)
```

### query
全件取得

```go
func findAll(db *gorm.DB) []User {
	var allUsers []User
	db.Find(&allUsers)
	return allUsers
}

func main() {
  ...
  fmt.Println(findAll(db)) // [山田太郎(25) 田中花子(22)]
}
```

ID昇順で一番初めのユーザー取得

```go
func firstUserByID(db *gorm.DB) User {
	var firstUser User
	db.First(&firstUser)
	return firstUser
}

func main() {
  ...
  fmt.Println(firstUserByID(db)) // 山田太郎(25)
}
```

IDで検索

```go
func findByID(db *gorm.DB, id int) User {
	var user User
	db.First(&user, id)
	return user
}

func main() {
  fmt.Println(findById(db, 2))   // 田中花子(22)
  // 存在しない場合は初期値が設定される
  fmt.Println(findByID(db, 100)) // (0)
}
```

名前で検索

```go
func findByName(db *gorm.DB, name string) []User {
	var user []User
	db.Where("name = ?", name).Find(&user)
	return user
}

func main() {
  ...
  fmt.Println(findByName(db, "山田太郎")) // [山田太郎(25)]
}
```

### update
IDをキーに更新

```go
func updateWhereID(id int, db *gorm.DB) {
	db.Model(&user).Where("id = ?", id).Update("age", 30)
}

func main() {
  ...
  updateWhereID(1, db)
	fmt.Println(findAll(db)) // [山田太郎(30) 田中花子(22)]
}
```

全て更新

```go
func updateAll(db *gorm.DB) {
	db.Model(&user).Update("age", 22)
}

func main() {
  updateAll(db)
  fmt.Println(findAll(db)) // [山田太郎(22) 田中花子(22)]
}
```

### delete
IDをキーに論理削除

```go
func deleteByID(id int, db *gorm.DB) {
	db.Where("id = ?", id).Delete(user)
}

deleteByID(1, db)
fmt.Println(findAll(db)) // []
```

もしモデルが`DeletedAt`フィールドを持っていれば自動的に論理削除になり、<br>
テーブル上にはレコードは残っているが、`deleted_at`に削除された時間が入りこのレコードはGoでは取得されない。<br>
実際に実行されているのは`update`文になる。

- テーブル

```sql
-- before
select * from users;
+----+---------------------+---------------------+------------+--------------+------+------+
| id | created_at          | updated_at          | deleted_at | name         | age  | sex  |
+----+---------------------+---------------------+------------+--------------+------+------+
|  1 | 2019-04-06 04:33:52 | 2019-04-06 04:33:52 | NULL       | 山田太郎     |   25 | 男   |
|  2 | 2019-04-06 04:33:52 | 2019-04-06 04:33:52 | NULL       | 田中花子     |   22 | 女   |
+----+---------------------+---------------------+------------+--------------+------+------+
2 rows in set (0.00 sec)

-- after
select * from users;
+----+---------------------+---------------------+---------------------+--------------+------+------+
| id | created_at          | updated_at          | deleted_at          | name         | age  | sex  |
+----+---------------------+---------------------+---------------------+--------------+------+------+
|  1 | 2019-04-06 04:33:52 | 2019-04-06 04:33:52 | 2019-04-06 04:35:29 | 山田太郎     |   25 | 男   |
|  2 | 2019-04-06 04:33:52 | 2019-04-06 04:33:52 | 2019-04-06 04:35:29 | 田中花子     |   22 | 女   |
+----+---------------------+---------------------+---------------------+--------------+------+------+
2 rows in set (0.00 sec)
```

## 参考
- [GORM Guides](http://gorm.io/docs/)
- [Declaring Models](http://gorm.io/docs/models.html)
- [Migration](http://gorm.io/docs/migration.html)
- [GORM](http://doc.gorm.io/)
- [【GORM】Go言語でORM触ってみた
](https://qiita.com/chan-p/items/cf3e007b82cc7fce2d81)