GORMでデータベースを操作してみる

「Tech Do Advent Calendar 2018」25日目、最終日です。
今回はgolangのORMライブラリであるgormの使い方を簡単にご紹介します。
この記事は[GORM公式ページ](http://gorm.io/)を参考にしています。



# ORM(オブジェクト関係マッピング)とは？
[wikipedia](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%96%A2%E4%BF%82%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0)より
> オブジェクト関係マッピング（英: Object-relational mapping、O/RM、ORM）とは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。オブジェクト関連マッピングとも呼ぶ。実際には、オブジェクト指向言語から使える「仮想」オブジェクトデータベースを構築する手法である。オブジェクト関係マッピングを行うソフトウェアパッケージは商用のものもフリーなものもあるが、場合によっては独自に開発することもある。

オブジェクト言語からデータベースへデータを渡すための橋渡しをするラッパー的なもの、らしいです。
golang ORM 略してgormです。
より詳しい説明は割愛。



# 説明の準備
今回説明に利用するデータはこちら。

テーブル名：**employee**

| 論理名       | 物理名        | データ型     | 制約等           |
| ------------ | ------------- | ------------ | ---------------- |
| 社員番号     | emp_num       | integer      | 主キー、not null |
| 社員名       | emp_name      | varchar(255) | not null         |
| 社員名のカナ | emp_name_kana | varchar(255) |                  |
| 年齢         | age           | integer      |                  |
| 入社日       | hire_date     | timestamp    | not null         |
| 登録日       | inserted_date | timestamp    | not null         |



データ

| emp_num | emp_name | emp_name_kana | age  | hire_date  | inserted_date |
| ------- | -------- | ------------- | ---- | ---------- | ------------- |
| 1       | hoge     | ホゲ          | 22   | 2018/04/01 | 2018/12/25    |
| 2       | foo      | フー          | 30   | 2018/12/24 | 2018/12/25    |





# とりあえず使ってみる
**gormをインストール**

```go
go get -u github.com/jinzhu/gorm
```



**操作対象となるデータの作成**

```go
//構造体を定義
type Employee struct {
	EmpNum       int            `gorm:"primary_key" "column:emp_num"`
	EmpName      string         `gorm:"column:emp_name"`
	EmpNameKana  sql.NullString `gorm:"column:emp_name_kana"`
	Age          sql.NullInt64  `gorm:"column:age"`
	HireDate     time.Time      `gorm:"column:hire_date"`
	InsertedDate time.Time      `gorm:"column:inserted_date"`
}
```

構造体の名称を**テーブル名と同じ「employee」**に。
各フィールド変数を、「**{変数名} {型名} {gormの定義}**」の形式で定義していきます。
gormの定義として、プライマリーキー、NOT NULL制約、型、カラム名等のタグを設定することが可能です。



注意点として、変数にNULL値が格納される場合、それに適応した変数型で定義する必要があります。
例：varchar型がNULL　→　**sql.NullString**型
　　timestamp型がNULL　→　**pq.NullTime**型　　

```go
//sql.NullStringの構造
type NullString struct {
    String    string
    Valid    bool
}
```



**データベースのコネクション取得**

```go
db, err := gorm.Open("postgres", 
                     "host=localhost port=5432 user=hoge password=hogehoge dbname=hoge_db sslmode=disable")
	if err != nil {
        fmt.Println("error")
	}

//処理完了後クローズ
defer db.Close()
```

これで準備は完了。



**Selectしてみる**

```go
//データを格納する変数を定義
employee := []Employee{}

//全取得
db.Find(&employee)

//表示
for _, emp := range employee {
    fmt.Println(emp.EmpNum)
    fmt.Println(emp.EmpName)
    fmt.Println(emp.EmpNameKana.String)
    fmt.Println(emp.Age.Int64)
    fmt.Println(emp.HireDate.Time)
    fmt.Println(emp.InsertedDate)
}
```

結果

```
1
hoge
ホゲ
22
2018-04-01 00:00:00 +0000 +0000
2018-12-25 00:00:00 +0000 +0000
```



## 実行例

実行例を少しだけ紹介

### Select
最初の一行を取得

```go
db.First(&employee)
```

全取得

```go
db.Find(&employee)
```

条件に合致するデータを取得

```go
db.Where("empName = ?", "hoge").Find(&employee)
```

条件に合致するデータを取得2

```go
db.Where("age between ? and ?" "20", "30").Find(&employee)
```

いずれかの条件に合致するデータを取得

```go
b.Where("empName = ?", "hoge").Or("empName = ?", "foo").Find(&employee)
```

取得カラムを指定して取得

```go
db.Select("empName, hire_date").Find(&employee)
```

order by

```go
db.Order("age desc").Find(&employee)
```

offset&limit

```go
db.Offset(1).Limit(3).Find(&employee)
```

件数をカウント

```go
var count int
db.Where("age > 20").Count(&count)
```

group&having

```go
db.Select("empName, age").Group("age").Having("age > 25").Find(&employee)
```

Join

```go
db.Table("employee emp")Join("inner join publisher pub on emp.publisher_id = pub.publisher_id")
```



### Insert

データを挿入

```go
//挿入するデータを定義
employee := Employee{EmpNum: 3,
                      EmpName: "bar",
                      EmpNameKana: sql.NullString{"バー", true},
                      Age: sql.NullInt64{int64(33), true},
                      HireDate: time.Now(),
                      InsertedDate: time.Now()
                     }

db.Create(&employee)
```



### Update

データを更新

```go

//更新するデータを定義
employee := Employee{EmpNum: 3,
                      EmpName: "bar",
                      EmpNameKana: sql.NullString{"バー", true},
                      Age: sql.NullInt64{int64(33), true},
                      HireDate: time.Now(),
                      InsertedDate: time.Now()
                     }

db.Save(&employee)
```



### Delete

データを削除

```go
db.Delete(&employee)
```

その他用法は[公式ページ](http://gorm.io/docs/)で確認してください。


## おまけ：開発中の失敗例と解決方法

実際に開発をしている中で想定通り動作しなかった例とその解決方法を挙げます。



### 取得対象のテーブル名が複数形になってしまう
取得したいテーブルは「employee」だが、実際に実行されるSQL分では「employees」になってしまっていたケース。

```go
type employee struct{
    ...
}

//理想
select * from employee;

//実際
select * from employees;
```

原因は、gormのデフォルト設定で扱うテーブルは全て複数形で扱うようになっているため。

#### 解決方法1:単数形で扱うように設定する

下記の記述を追加することで単数形のままSQLが実行されます。

```go
db.SingularTable(true)
```

#### 解決方法2:データベースの定義を複数形にする

gormによって複数形で扱われてしまうならデータベース側の定義を変えてしまおうという話です。
employee →employees





## 所感

gormを使わず素のSQL文を長々と書くよりも直感的に理解しやすい。
時間短縮にもなって良い。
gormが使える状況なら悩むことなく使うことをおすすめします。