【GORM】Go言語でORM触ってみた

# はじめに
趣味でネイティブアプリケーションのAPIをGo言語で開発中の駆け出しエンジニアです。
普段は機械学習周りのデータサイエンスを勉強しています。ということもあり、最近初めてORMという存在を知ったので、ORMは何なのか、またGo言語のORMであるGORMの最低限の操作をまとめておこうと思います。

少しでも役に立てれば幸いです。

===2018/2/19_追記===
最近、この記事のいいねが伸びてきているのでGo言語でRedisというKVSを触った記事を書いてみました。よろしければ、そちらもどうぞ。
[【Redis】Go言語で高速呼び出しKVS【Redigo】](https://qiita.com/chan-p/items/5c5e7cc1e966f8a90422)

また、pythonのORMにもまとめてみました。
[【dataset】pythonでDB処理を楽チンに！【ORM】【python】](https://qiita.com/chan-p/items/048afbf1855f40c37922)

===2018/12/21_追記===
Gormを使ってAPI開発してみました。
フレームワークはechoを使っています。
[【echo】Go言語でDB操作やファイル操作するミニマムAPI開発【Gorm】](https://qiita.com/chan-p/items/8cfaaf31720d6f025f29)

# ORMとは？
Object-Relational-Mappingの略で、日本語ではオブジェクト関係マッピングと呼ばれるものです。具体的には、オブジェクト指向プログラムとリレーショナルデータベースをつなぐ役割をしてくれます。
ORMを使用することでより直感的にRDBの操作をすることができると思います。

自分もORMを使う前はSQL文をベタ書きしていたのですが、ORMを使用することでコードが劇的にキレイになりました。

# GORM
## 環境
MacOS  ver10.11.6
golang ver1.7.1

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

## 操作
GORMでRDBを操作するための最低限の構文をまとめます。

### RDBとの接続
以下のコードでRDBとの接続を確立します。

```go
import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
)

func gormConnect() *gorm.DB {
  DBMS     := "mysql"
  USER     := "root"
  PASS     := "####"
  PROTOCOL := "tcp(##.###.##.###:3306)"
  DBNAME   := "##"
 
  CONNECT := USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME
  db,err := gorm.Open(DBMS, CONNECT)

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

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

今回データベースはMySQLにしました。なので、gorm.Openの第一引数はmysqlを渡します。USERはmysqlに接続するアカウントを指定し、PASSにはそのパスワードを入力します。PROTOCOLには接続先のIPとポート番号を指定して、最後に接続したいDBを指定します。
以下、書き方の例です。
Ex ) gorm.Open("mysql", "root:root999@tcp(192.168.11.321:3306)/testdb")
これで実行でエラーが出なければ接続の確立完了です。

### SELECT文(単一レコード)
単一レコードと複数レコードをselectで引っ張ってくるのは書き方が違うので分けて書きます。まずは、単一レコードをselectする場合です。

例で扱うテーブルを以下、eventsテーブルとします。

![スクリーンショット 2016-12-30 1.41.45.png](https://qiita-image-store.s3.amazonaws.com/0/148317/9753a749-3fcb-be57-af6f-4e03cd700ed9.png)

このテーブルを元にモデルを定義します。一応、json形式の宣言もしてあります。

```go
type event struct {
  Id          int    `json:id`
  UserId     int    `json:user_id`
  Summary     string `json:summary`
  Dtstart     string `json:dtstart`
  Dtend       string `json:dtend`
  Description string `json:description`
  Year        int    `json:year`
  Month       int    `json:month`
  Day         int    `json:day`
}
```

ここで重要なのが、定義したモデルの名前とテーブル名が単数と複数という関係であることです。GORMではテーブルの指定がないとテーブル名を構造体の名前の複数形で参照します。今回であれば、eventsテーブルに対してeventという構造体を定義しました。さらにデータ型もテーブル情報と合わせてください。

次に単一レコードのSELECTです。

```go
func main(){
  db := gormConnect()
  defer db.Close()

  // 構造体のインスタンス化
  eventEx := event{}
  // IDの指定
  eventEx.Id = 12
  // 指定したIDを元にレコードを１つ引っ張ってくる
  db.First(&eventEx) 
  // もしくはwhere句っぽく
　　　　db.First(&eventEx, "summary=?", "test")
}
``` 

基本的に構造体の情報を元にテーブルを参照して、引数に渡した構造体の情報を埋めたり、削除したりします。
この例では、eventsテーブルのidが12のレコードを引っ張ってきています。idは主キー(上図)なので、指定すれば確実にidが12のレコードを1つ取ってこれます。eventExを出力してみるとidが12のレコードの情報で埋まっているはずです。つまり、eventExが1レコードを表しています。最初に指定するのはIDに限らず、レコードを1つ特定できる情報を与えれば大丈夫です。

### SELECT文(複数レコード)
次に複数レコードを引っ張ってきます。

```go
func main(){
  db := gormConnect()
  defer db.Close()

  // 構造体のインスタンス化
  eventsEx := []event{}
  // 指定した条件を元に複数のレコードを引っ張ってくる
  db.Find(&eventsEx, "user_id=?", 2) 
}
``` 
db.Firstの場合との違いは、引数の変数が配列である点と条件の指定の仕方です。引っ張ってきた複数のレコードを格納するために構造体を配列でインスタンス化します。そして、先ほどは直接レコード情報の何かを指定していましたが、今回はdb.Findの引数で条件を指定します。要はwhere句と同じです。なので、複数の条件で指定する時は以下のようになります。

```go
func main(){
  db := gormConnect()
  defer db.Close()

  // 構造体のインスタンス化
  eventsEx := []event{}
  // 指定した複数の条件を元に複数のレコードを引っ張ってくる
  db.Find(&eventsEx, "user_id=? and year=?", 2, 2016) 
}
``` 
こうすることで、user_idが2でかつyearが2016のevent情報を取ってくることができます。配列の1つ1つにeventの1レコードが格納されているはずです。

### INSERT文
次に頻繁に使うであろうINSERT文です。SQL文をベタ書きすると面倒だった部分もORMを使うこと1行でかけます。

```go
func main(){
  db := gormConnect()
  defer db.Close()

  // 構造体のインスタンス化
  eventEx := event{}
  // 挿入したい情報を構造体に与える
  eventEx.Id = 0
  eventEx.UserId = 2
  // ・
  // ・
  eventEx.day = 19 
  // INSERTを実行
  db.Create(&eventEx)
}
``` 

構造体の情報をすべて埋めた上でdb.Createによってレコードを生成することができます。ここで重要な点はidが0である点です。今回、idはAUTO_INCREMENTになっているのでidを指定する必要はありません。なので、idを0にしておくと生成されたレコードは自動的にidが割り当てられて生成されます。

### DELETE文
次にレコードの削除であるDELETE文です。ここは慎重にやることをオススメします。自分は試行錯誤しながらやった結果、何回かテーブル情報をすべて吹っ飛ばしました。注意すべきなのは、引数で渡すレコード情報です。削除したいレコード情報が不十分のままdeleteを行うとテーブル内のデータすべて消えます(笑)

```go
func main(){
  db := gormConnect()
  defer db.Close()

  // 構造体のインスタンス化
  eventEx := event{}
  // 削除したいレコードのIDを指定
  eventEx.Id = 2
  // このままdeleteを行うとテーブル内のレコードすべてが消える
  // db.Delete(&eventEx)
  // まずは削除したいレコードの情報を埋める
  db.First(&eventEx)
  // 完全にレコードを特定できる状態で削除を行う
  db.Delete(&eventEx) 
}
``` 

このように一度削除したいレコード情報をdb.Firstで埋めてから削除を行うことで指定した1レコードを削除できます。複数レコードを消したいときは、一度にやらずdb.Findで削除したいレコード群を引っ張てきてfor文で1レコードずつ削除すると確実だと思います。

#### 追記(2018/7/19)
PRIMARY KEYが空だと全削除になるとコメントをいただきました。
なので以下のように直すと全削除を回避できるようです。

```
type event struct {
  Id          int    `gorm:"primary_key"`
  ...
}
```

http://gorm.io/docs/delete.html



### UPDATE文
最後にUPDATE文です。UPDATEするときは、更新したい古いレコードの指定と更新後の情報を持ったレコードが必要となります。

```go
func main(){
  db := gormConnect()
  defer db.Close()

  // 構造体のインスタンス化
  eventExBefore := event{}
  // 更新したいレコードを指定する
  eventExBefore.Id = 2
  // 更新後のレコード情報の生成(yearを2016から2015に更新)
  eventExAfter := eventExBefore
  db.First(&eventExAfter)
  eventExAfter.year = 2015
  // 更新を実行
  db.Model(&eventExBefore).Update(&eventExAfter)
  // 上記で更新できない場合は
  db.Save(&eventExAfter)
}
``` 

少しわかりにくくなってしまいましたが、更新前と更新後のレコード情報が必要な点が重要となります。db.Modelで更新したいレコードをidで指定します。(eventExBeforeにはid情報だけ与えている)
eventExAfterは、一度Firstによって更新前の情報で埋めます。その後、更新したいところだけを更新後の情報に書き換えます。こうすることで、直感的にレコードの指定と更新がわかると思います。

以下、コードのまとめです。

```go:sample.go
package main

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

type event struct {
  Id          int    `json:id`
  UserId     int    `json:user_id`
  Summary     string `json:summary`
  Dtstart     string `json:dtstart`
  Dtend       string `json:dtend`
  Description string `json:description`
  Year        int    `json:year`
  Month       int    `json:month`
  Day         int    `json:day`
}

func gormConnect() *gorm.DB {
  DBMS     = "mysql"
  USER     = "root"
  PASS     = "####"
  PROTOCOL = "tcp(##.###.##.###:3306)"
  DBNAME   = "##"
 
  CONNECT = USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME
  db,err := gorm.Open(DBMS, CONNECT)

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

func main(){
  db := gormConnect()
  defer db.Close()
  
  // INSERT文
  // 構造体のインスタンス化
  eventEx := event{}
  // 挿入したい情報を構造体に与える
  eventEx.Id = 0
  eventEx.User_Id = 2
  // ・
  // ・
  eventEx.day = 19 
  // INSERTを実行
  db.Create(&eventEx)

  // SELECT文(単一)
  // 構造体のインスタンス化
  eventEx := event{}
  // IDの指定
  eventEx.Id = 12
  // 指定したIDを元にレコードを１つ引っ張ってくる
  db.First(&eventEx) 

  // SELECT文(複数)
  // 構造体のインスタンス化
  eventsEx := []event{}
  // 指定した複数の条件を元に複数のレコードを引っ張ってくる
  db.Find(&eventsEx, "user_id=? and year=?", 2, 2016)

  // DELETE文
  // 構造体のインスタンス化
  eventEx := event{}
  // 削除したいレコードのIDを指定
  eventEx.Id = 2
  // このままdeleteを行うとテーブル内のレコードすべてが消える
  // db.Delete(&eventEx)
  // まずは削除したいレコードの情報を埋める
  db.First(&eventEx)
  // 完全にレコードを特定できる状態で削除を行う
  db.Delete(&eventEx) 

  // UPDATE文
  // 構造体のインスタンス化
  eventExBefore := event{}
  // 更新したいレコードを指定する
　eventExBefore.Id = 2
  // 更新後のレコード情報の生成(yearを2016から2015に更新)
　eventExAfter := eventExBefore
  db.First(&eventExAfter)
  eventExAfter.year = 2015
  // 更新を実行
  db.Model(&eventExBefore).Update(&eventExAfter)
}
```


以上がORMを使った最低限のRDBの操作になります。多少まわりくどい書き方をしていますが、これでまずは動かしてみるのが良いと思います。最後にマイグレーションについてもまとめておきます。

### Migration(マイグレーション)
テーブルの作成や削除をORMのマイグレーション機能で行うことができます。

```go
type Task_Tag struct {
  Id      int `json:id sql:AUTO_INCREMENT`
  TaskId int `json:task_id`
  TagId  int `json:tag_id`
}

func main(){
  db := gormConnect()
  defer db.Close()

  db.CreateTable(&task_tag)
}
``` 

このようにテーブル情報を定義して、db.CreateTableするだけテーブルを作成することできます。sql:の部分でカラム情報をくわしく設定することできます。ちなみに下図が実際に生成されたテーブルです。

![スクリーンショット 2016-12-30 3.22.33.png](https://qiita-image-store.s3.amazonaws.com/0/148317/70595569-a18c-b498-31d1-305434bd44e9.png)

# 参考URL
[Go言語のgormでORMしてみた](http://qiita.com/y-kishibata/items/954f13e3beba3851180f)
[GORM Guides](http://jinzhu.me/gorm/)