それが僕には楽しかったんです。

いろんなレイヤーに居ます

Golangのdatabase/sqlパッケージを使っていく

はじめに

どうも、最近マイクラをかってしまい無限に時間を溶かしているけんつです。

何故かgolangが書きたくなる衝動に駆られているのですがその勢いでORMを自作しようとしたときに色々と謎が深くなってきたので
まず今まで使ってこなかった database/sql パッケージを使ってみようかと思いたったので書きます

参考文献は以下のページ
http://go-database-sql.org/overview.html

Database driver をインポートする

Golang で database/sql パッケージを使ってDBを操作するには必要となるドライバーを自分で持ってくる必要がある。
今回は対象となるDBがMySQLなので、MySQL用のドライバーをもってくる。といってもいくつかあるのでチュートリアル通り "github.com/go-sql-driver/mysql" を持ってくる

$ go get github.com/go-sql-driver/mysql

これでDBにアクセスする準備ができた。

データベースにアクセスする

DBにアクセスするには以下のようなコードを書く。

package main

import (
    "database/sql"
   _ "github.com/go-sql-driver/mysql"
)

func main() {
    //[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
    db, err := sql.Open("mysql", "sample:sample@tcp(127.0.0.1:3306)/sample")

    if err != nil {
        panic(err)
    }

    defer db.Close()
}

まず、はじめにsql.Openを呼ぶ。
ここでは利用するDBドライバを指定し、DSNを指定の形式に沿って記述する。
DSNでは dbname が require になっているがあとは必要に応じて記述する形となる。

この sql.Open 時に何かしら接続できないなどの事象が発生するとerrが返ってくるためこの部分はエラーハンドリングするべきである。
さらに接続といっても、この関数をコールし正常に終了したところで実はデータベースへの接続は確率されていない。
そのため、さらに以下の様に db.Ping() をコールして疎通確認をするべき(らしい

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

加えてこの sql.DB オブジェクトのライフサイクルは長くなるように設計されている。
そのため、Open, Close を頻繁に呼び出すのではなくライフサイクルの短い関数に引数と渡すことが推奨されている(シングルトンみたいな感じ
なぜライフサイクルが長く設計されているかというと、逆に短くしてしまうとネットワークリソースを食いつぶしてしまったりTCPコネクションがTIME_WAIT状態になって残ってしまうなどの問題があるから(らしい

SQLの実行と結果の取得

まず、参考サイトと同じ構成のテーブルを作る。insert文は適当にgoでコード書いてsqlファイルにくっつけた。

CREATE TABLE users (
    id INT NOT NULL AUTO_INCREMENT,
    name varchar(30) NOT NULL,
    PRIMARY KEY (id)
);

INSERT INTO users(`name`) value ('John');
INSERT INTO users(`name`) value ('Rose');
INSERT INTO users(`name`) value ('Jack');
INSERT INTO users(`name`) value ('Mary');
INSERT INTO users(`name`) value ('Olivia');
INSERT INTO users(`name`) value ('John');
INSERT INTO users(`name`) value ('Rose');
INSERT INTO users(`name`) value ('Jack');
INSERT INTO users(`name`) value ('Mary');
INSERT INTO users(`name`) value ('Olivia');

ここまでやったら database/sql パッケージを使ってSQLを実行するコードを次のように追加する。

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func main() {

    //[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
    db, err := sql.Open("mysql", "sample:sample@tcp(127.0.0.1:3306)/sample")
    if err != nil {
        panic(err)
    }

    err = db.Ping()
    if err != nil {
        panic(err)
    }
    defer db.Close()

    var id int
    var name string
    rows, err := db.Query("select * from users where id < ?", 6);
    if err != nil {
        panic(err)
    }
    defer rows.Close()

    for rows.Next() {
        err := rows.Scan(&id, &name)
        if err != nil {
            panic(err)
        }
        fmt.Println(id, name);
    }

    err = rows.Err()
    if err != nil {
        panic(err)
    }
}

ひとつずつ解説していく。

まず、db.Open() が正常終了したら db.Query でSQLを実行する。値を入れたいときは ? をつかう。
これにより、そのSQLの結果が行単位かエラーで返ってくるのでハンドリングする。

次にその行の集合は rows.Nextでアクセスできる。
ここで rows.Scan() を使い、結果を変数に代入していく。

そして最後には行に対するイテレータが無事に終了していることを確認するべきらしい。

予めクエリを用意しておく

db.Queryで実行するのではなく予めプレースホルダーを含むSQLなどを用意しておき実行時にその値だけを差し替えるなんていうこともできる。

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func main() {

    db, err := sql.Open("mysql", "sample:sample@tcp(127.0.0.1:3306)/sample");
    if err != nil {
        panic(err)
    }

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


    stmt, err := db.Prepare("select * from users where id < ?")
    if err != nil {
        panic(err)
    }
    defer stmt.Close()

    var (
        id int
        name string
    )
    rows, err := stmt.Query(3)
    for rows.Next() {
        err := rows.Scan(&id, &name)
        if err != nil {
            panic(err)
        }
        fmt.Println(id, name)
    }
    defer rows.Close()

    if err = rows.Err(); err != nil {
        panic(err)
    }
}

さいごに

とりあえず、基本的な部分はまとめたので次はトランザクション周りをまとめていきたい。