Edited at

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


はじめに

趣味でネイティブアプリケーションのAPIをGo言語で開発中の駆け出しエンジニアです。

普段は機械学習周りのデータサイエンスを勉強しています。ということもあり、最近初めてORMという存在を知ったので、ORMは何なのか、またGo言語のORMであるGORMの最低限の操作をまとめておこうと思います。

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

===2018/2/19_追記===

最近、この記事のいいねが伸びてきているのでGo言語でRedisというKVSを触った記事を書いてみました。よろしければ、そちらもどうぞ。

【Redis】Go言語で高速呼び出しKVS【Redigo】

また、pythonのORMにもまとめてみました。

【dataset】pythonでDB処理を楽チンに!【ORM】【python】

===2018/12/21_追記===

Gormを使ってAPI開発してみました。

フレームワークはechoを使っています。

【echo】Go言語でDB操作やファイル操作するミニマムAPI開発【Gorm】


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との接続を確立します。

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

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

type event struct {

Id int `json:id`
User_Id 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です。

func main(){

db := gormConnect()
defer db.Close()

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

基本的に構造体の情報を元にテーブルを参照して、引数に渡した構造体の情報を埋めたり、削除したりします。

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


SELECT文(複数レコード)

次に複数レコードを引っ張ってきます。

func main(){

db := gormConnect()
defer db.Close()

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

db.Firstの場合との違いは、引数の変数が配列である点と条件の指定の仕方です。引っ張ってきた複数のレコードを格納するために構造体を配列でインスタンス化します。そして、先ほどは直接レコード情報の何かを指定していましたが、今回はdb.Findの引数で条件を指定します。要はwhere句と同じです。なので、複数の条件で指定する時は以下のようになります。

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行でかけます。

func main(){

db := gormConnect()
defer db.Close()

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

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


DELETE文

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

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するときは、更新したい古いレコードの指定と更新後の情報を持ったレコードが必要となります。

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によって更新前の情報で埋めます。その後、更新したいところだけを更新後の情報に書き換えます。こうすることで、直感的にレコードの指定と更新がわかると思います。

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


sample.go

package main

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

type event struct {
Id int `json:id`
User_Id 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を元にレコードを1つ引っ張ってくる
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のマイグレーション機能で行うことができます。

type Task_Tag struct {

Id int `json:id sql:AUTO_INCREMENT`
Task_Id int `json:task_id`
Tag_Id 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


参考URL

Go言語のgormでORMしてみた

GORM Guides