かけ足で学ぶ Golang その4 〜データベース操作〜


f:id:Naotsugu:20170417231928p:plain

前回は H2 データベースの準備を行いました。

etc9.hatenablog.com

今回はデータベース操作を行っていきます。

ドライバのインポート

postgres のドライバはいろいろありますが、ここでは github.com/lib/pq を使います。

go get しましょう。

$ cd $GOPATH
$ go get github.com/lib/pq

ドライバが取得できたら、ドライバ github.com/lib/pq と、組込の database/sql をインポート指定します。

import (
    "database/sql"
    _ "github.com/lib/pq"
)

github.com/lib/pq はプレースホルダとして _ を指定してインポートします(変数名を指定し、パッケージ名が衝突する場合にエイリアス名を付けたインポートができます)。

Go では未使用の変数があるとコンパイルエラーになります。 インポートしたドライバは直接プログラムから利用しないため、プレースホルダとして _ を指定し、変数を利用しないことを宣言しています。

"github.com/lib/pq" でパッケージをインポートすると、pq パッケージの init() 関数が自動的に呼ばれます。

Go では main関数とinit関数が特別に予約されており、インポート時にパッケージに属する init() 関数が自動的にコールされる仕組みになっています。

では pq パッケージにある init() 関数を見てみましょう。

type Driver struct{}
func (d *Driver) Open(name string) (driver.Conn, error) {
    return Open(name)
}

func init() {
    sql.Register("postgres", &Driver{})
}

sql.Register()postgres という名前でドライバを登録しています。

ここで Open 関数の頭に見慣れない (d *Driver) という定義がありますね。 これは Driver 構造体のメソッドとして Open 関数を定義するものとなってます。

関数の頭にレシーバとなるあらゆる型を指定でき、そうすると関数がその型に属するメソッドとして扱うことができるようになります。

データベース接続

sql.Open() でデータベースコネクションを取得します。

db, err := sql.Open("postgres",
    "postgres://sa:pass@localhost:5435/~/testdb?sslmode=disable")
if err != nil {
    panic(err)
}
defer db.Close()

H2 の PG server のポートはデフォルトで 5435 となります。

TCP server running at tcp://localhost:9092 (only local connections)
PG server running at pg://localhost:5435 (only local connections)
Web Console server running at http://localhost:8082 (only local connections)

データベースファイルは ~/testdb つまりホームディレクトリに testdb という名前で作成されます。

テーブル作成とレコードの挿入

db.Exec() にてSQLを実行できます。

db.Exec(`
  CREATE TABLE users (
      id    INTEGER auto_increment PRIMARY KEY,
      name  VARCHAR(20),
      email VARCHAR(255)
  );
`)

複数行にわたる文字列は で定義できます。ここでは users テーブルを作成しています。 id列はauto_increment` とすることで自動採番されます。

同様に insert 文は以下のように実行できます。

db.Exec("INSERT INTO users(name, email) VALUES($1, $2);", "Thome", "thome@example.com")

Query

db.Query() を使います。

rows, err := db.Query(`SELECT id, name, email FROM users`)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
    var id int
    var name string
    var email string
    rows.Scan(&id, &name, &email)
    log.Printf("[%v], %v, %v", id, name, email)
}

Rows 構造体から Scan() で結果を取得することができます。

h2パッケージの作成

前回分と合わせて h2 パッケージにまとめます。

h2 というディレクトリの中に h2.go というファイルを作り、以下とします。

package h2

import (
    "log"
    "os"
    "net/http"
    "io"
    "os/exec"
    "path/filepath"
    _ "github.com/lib/pq"
    "database/sql"
    "time"
)

const (
    url  = "http://central.maven.org/maven2/com/h2database/h2/1.4.194/h2-1.4.194.jar"
    path = "lib/h2-1.4.194.jar"
)

func download(url string, path string) error {

    out, err := os.Create(path)
    if err != nil {
        return err
    }
    defer out.Close()

    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if _, err := io.Copy(out, resp.Body); err != nil {
        return err
    }

    return nil
}


func StartH2() {

    if _, err := os.Stat(path); os.IsNotExist(err) {
        os.MkdirAll(filepath.Dir(path), 0755)
        if err := download(url, path); err != nil  {
            panic(err)
        }
    }

    cmd := exec.Command("java", "-cp",  path, "org.h2.tools.Server")
    cmd.Start()
    time.Sleep(3 * time.Second)
    log.Printf("H2 Started. PID[%v]", cmd.Process.Pid)
}


func StopH2() {
    err := exec.Command("java", "-cp",  path,
        "org.h2.tools.Server", "-tcpShutdown", "tcp://localhost:9092").Run()
    if err != nil {
        log.Fatal(err)
    }
}

func InitDb() {

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

    db.Exec(`
      CREATE TABLE users (
          id    INTEGER auto_increment PRIMARY KEY,
          name  VARCHAR(20),
          email VARCHAR(255)
      );
    `)
}

func Db() *sql.DB {
    db, err := sql.Open("postgres",
        "postgres://sa:pass@localhost:5435/~/testdb?sslmode=disable")
    if err != nil {
        panic(err)
    }
    return db
}

これで別パッケージから以下のようにデータベースの起動とテーブル生成が出来るようになりました。

h2.StartH2()
h2.InitDb()


次回は etc9.hatenablog.com