GORMを使ってみる[Go]

# はじめに
`Gorm`って使ったことありますか？
Goでは、コードないにsqlクエリを書いて`github.com/go-sql-driver/mysql`を使えば、データベース操作が行えると思います。

しかし、Djangoなどのmodelを定義するだけでデータベース操作が行えてしまうフレームワークのみを過去に触っていた人は、`sqlクエリ`の書き方がわからず苦戦すると思います。
それにいくらsqlが書けたとしても、長々と`sql`を書きたくないですよね。

なので、今回はそれを解決する`GORM`を使ってみようと思います。

# GORMとは
`GORM`とは、Go言語のORMライブラリです。

ORMって言われてもわからないですよね。

まずはORMから説明します。

### `ORM`とは

オブジェクト関係マッピング(Object-Relational Mapper)の略称で、オブジェクト指向におけるクラスや構造体とデータベースを紐付ける技術のことを指します。Djangoでモデルを定義するだけで、データベース操作が行えたのは標準でORMが使われていたからです。

`メリット`は

- オブジェクト指向におけるクラスを使った再利用性が高いこと
- クエリの自動生成があるため簡単に操作できる

`デメリット`は
- sqlクエリとは異なり、言語やライブラリ毎に知識を得ることが必要
- 複雑な操作はクエリを学ぶ必要がある


という感じだと思います。

個人的には、クエリ書く方がどこでも使えますし、実際に`mysql-client`とかでデータベースの中身に入ってデータを確認する時には必要なので好きです。ただ自分はsql書くよりも先にDjangoを触って`ORM`によるデータベース操作`ORM`の存在を全く理解せずに使って、楽であることは痛感したので良い技術だなと感じています。


# 環境を作る
### Dockerでの環境
今回は、Dockerでmysqlコンテナを立ち上げていこうと思います。
`docker-compose.yml`を下のように記述します。

```docker-compose.yml
services:
  db:
    image: mysql:8.0
    container_name: mysql_gorm
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - db-volume:/var/lib/mysql

volumes:
  db-volume:
```

あとは下のコマンドを打ちます。
```
docker-compose.yml
```

コンソールに下のような文言があれば立ち上がっています。
```
mysql_gorm  | 2024-08-15T02:58:51.493939Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.38'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.
```
:::note
この文言判断しづらいので、mysqlコンテナの中に入ってdbを操作できるか試した方が良さそうです。
:::

### GOでの環境
Goでのセットアップを行います。

Goのモジュールを以下のように初期化します。
```
go mod init <モジュール名>
```

モジュール名は自由に決めることが可能なので、私は下のようにしました。
```
go mod init github.com/maooz4426/go_gorm
```

`GORM`をインストールします
```
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
```

ここまで来たらコードを書いてみようと思います。

# コードを書いてみる

### レコードを入れる
下にPersonというスキーマを定義して、それをマイグレート、Jackさんのレコードを挿入した後にJackさんのレコードを取得するようなコードを書いてみました。

```go:main.go
package main

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

type Person struct {
	gorm.Model
	Name string
	Age  int
}

func main() {
	dsn := "user:password@tcp(localhost:3306)/db?parseTime=true"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("mysql can not open:", err)
	}

	db.AutoMigrate(&Person{})

	db.Create(&Person{
		Name: "jack",
		Age:  21,
	})

	var person Person

	result := db.First(&person, 1)
	if result.Error != nil {
		log.Fatal(result.Error)
	}
	fmt.Println(person.Name)
}


```

GORMを使う場合、`gorm`モジュールと使うデータベースようのドライバーが必要になります。なので今回は、`gorm.io/driver/mysql`と`gorm.io/gorm`をインポートしています。

DSNを`docker-compose.yml`で書いた環境変数から設定します。
>`DSN`とは
DataSorceNAMEのことで、データベース接続のために必要な識別子のことです

:::note
時間はbyte型で、GORMのcreated_at等の時間に関するカラムはtime.Time型となっていて、型が違うため`parseTime=true`をつけないとデータが取れないのでつけてください。
:::

そこからOpenメソッドでデータベースを接続します。

AutoMigrateメソッドでは、マイグレーションを自動で行っています。
>`マイグレーション`とは
データベースのスキーマ(構造）の更新を行うこと。
例えば、Userテーブルに利き手というカラム(要素)が追加されたら、それをデータベースに適応させる作業のことを言う

Createメソッドでレコードを作成しています。

Firstメソッドでは、レコードの読み込みを行なっており、primary-keyが１であるレコードをPerson型の変数personに読み込んでいます。

:::note
Firstメソッドでは、読み取った値を変数に入れて使う必要があります。
:::

実行してみると、

```
go run main.go
jack
```

うまく動作しました！

ここでmysqlの中に入ってみようと思います。
Docker Desktopの立ち上げたコンテナを選択して下のような画面に移動します。
![スクリーンショット 2024-08-15 19.13.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/10ad7739-3a13-4ea7-68f9-770d8f117776.png)

このターミナルに下のコマンドでmysqlに入ります。
```
mysql -h localhost -u user -p 
```

すると下のようにpasswordを入力するように言われるので`docker-compose.yml`に入力した環境変数のpasswordを使用します。
```
sh-5.1# mysql -h localhost -u user -p
Enter password: 
```
passwordが入力成功すると下のようになります。
```
mysql> 
```
そこから下のクエリを入力して行って、テーブルがどうなってるか確認します。
```
use db

show tables;
```

すると下のように表示されます。
```
+--------------+
| Tables_in_db |
+--------------+
| people       |
+--------------+
```

構造体でPersonと指定したのに、なぜかPeopleになっています。
GORMでは標準でテーブル名が複数形になります。

なのでここからレコードを確認するには、Personの複数形であるPeopleをテーブルで指定する必要があります。
```
mysql> SELECT * FROM people;
+----+-------------------------+-------------------------+-------------------------+------+------+
| id | created_at              | updated_at              | deleted_at              | name | age  |
+----+-------------------------+-------------------------+-------------------------+------+------+
|  1 | 2024-08-15 09:58:13.661 | 2024-08-15 10:00:35.084 | 2024-08-15 10:05:54.017 | jack |   21 |
+----+-------------------------+-------------------------+-------------------------+------+------+
```

### レコードを更新する
次にupdateしてみようと思います.

下のようにCreateメソッドをコメントアウトして、名前をJackからKenyに変えるコードを書いてみました。

```go:main.go
package main

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

type Person struct {
	gorm.Model
	Name string
	Age  int
}

func main() {
	dsn := "user:password@tcp(localhost:3306)/db?parseTime=true"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("mysql can not open:", err)
	}

	db.AutoMigrate(&Person{})

	//db.Create(&Person{
	//	Name: "jack",
	//	Age:  21,
	//})

	db.Model(&Person{}).Where(1).Update("name", "Keny")

	var person Person

	result := db.First(&person, 1)
	if result.Error != nil {
		log.Fatal(result.Error)
	}
	fmt.Println(person.Name)
}

```

`db.Model(&Person{}).Where(1).Update("name", "Keny")`の部分はクエリだと下と同じことを行なっています。
```
Update people SET name="Keny"
```

実行結果は下のようになり変わったようです。
```
go run main.go
Keny
```

### レコードを削除する
次はレコードを削除してみようと思います。
下のコードのように、いくつかコードをコメントアウトして、KenyのレコードをDeleteメソッドで削除しようと思います。
```go:main.go
package main

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

type Person struct {
	gorm.Model
	Name string
	Age  int
}

func main() {
	dsn := "user:password@tcp(localhost:3306)/db?parseTime=true"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("mysql can not open:", err)
	}

	db.AutoMigrate(&Person{})

	//db.Create(&Person{
	//	Name: "jack",
	//	Age:  21,
	//})

	//db.Model(&Person{}).Where(1).Update("name", "Keny")

	db.Delete(&Person{}, 1)

	var person Person

	result := db.First(&person, 1)
	if result.Error != nil {
		log.Fatal(result.Error)
	}

	fmt.Println(person.Name)
}

```

実行してみます。
```
go run main.go

2024/08/15 19:05:54 /Users/maoz/Documents/Go/go_grom/main.go:36 record not found
[0.643ms] [rows:0] SELECT * FROM `people` WHERE `people`.`id` = 1 AND `people`.`deleted_at` IS NULL ORDER BY `people`.`id` LIMIT 1
2024/08/15 19:05:54 record not found
exit status 1
```

削除できたようです。

# 最後に
ORMはとても便利で使いやすいのでぜひGoで開発している方は使ってみてください。
自分はsqlの知識が足りないので当分はクエリをコードに書いて行おうと思います。