post Image
【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()
}

今回データベースは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()

  // 構造体のインスタンス化
  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()

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

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

func main(){
  db := gormConnect()

  // 構造体のインスタンス化
  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()

  // 構造体のインスタンス化
  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()

  // 構造体のインスタンス化
  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()

  // 構造体のインスタンス化
  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()

  // 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()

  db.CreateTable(&task_tag)
}

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

スクリーンショット 2016-12-30 3.22.33.png

参考URL

Go言語のgormでORMしてみた
GORM Guides


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。