MySQLやPostgreSQL、SQL Server、Oracleなど多種多様なDBMSがあるのだが、環境構築に消耗したくないということでSQLite3を選択。
小規模、もしくは大規模なサービスをつくるならORMを使うより直接SQLite3を操作したほうが問題が少なくすみそう。
ということで、当記事ではgolangからライブラリなどを使わずSQLite3を操作する方法をまとめる。
※ ただしSQLite3のドライバは使う
環境は以下のとおり
- Mac OSX
- go v1.8.3
- sqlite3 v3.8.5
Go+SQLite3開発環境構築
まずはgoを動かすことができる開発環境を構築する。詳細は以下の記事を参照。
といってもすべてインストールする必要はない。
最低限、go v1.8.3だけでよい。パッケージ管理ツールのdepがあると便利だが必須ではない。
SQLite3については、Macならbrewでインストールすると簡単だ。
WindowsやLinuxについてはhttp://www.sqlite.org/からダウンロードし、インストールする。
SQLite3のドライバをインストールする
SQLite3ドライバはいくつかあるが、database/sqlインターフェースをサポートしているものは少ない。そこで今回はVim, golang界隈で有名?なmattn氏が作成したgo-sqlite3というドライバを使う。
$ go get github.com/mattn/go-sqlite3
depを使っている場合なら、main.goにimport文を追加し、以下のコマンドを実行する。
$ dep ensure
テーブルの作成
今回は開発用ディレクトリ直下にtest.dbというファイル(データベース)をつくる。
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
// データベースのコネクションを開く
db, err := sql.Open("sqlite3", "./test.db")
if err != nil {
panic(err)
}
// テーブル作成
_, err = db.Exec(
`CREATE TABLE IF NOT EXISTS "BOOKS" ("ID" INTEGER PRIMARY KEY, "TITLE" VARCHAR(255))`,
)
if err != nil {
panic(err)
}
sql.Open("DBドライバ名", "接続先")
でデータベースに接続できる。ファイルはなければ作成し、あればそれを使う。テーブル作成は
db.Exec(...)
でCREATE TABLE文を実行するだけ。Node.jsと違い非同期処理(Callback、Promise、Async/Awaitなど)を考えなくてするのは楽だ。
レコードの挿入
// データの挿入
res, err := db.Exec(
`INSERT INTO BOOKS (ID, TITLE) VALUES (?, ?)`,
123,
"title",
)
if err != nil {
panic(err)
}
// 挿入処理の結果からIDを取得
id, err := res.LastInsertId()
if err != nil {
panic(err)
}
データの挿入も同様にInsert文を書いて、?でバインドするだけ。またdb.ExecはResultを返すので、
Result.LastInsertId()
で挿入したレコードのIDを取得できる。※ テーブルの設定などにより取得できないこともある
複数レコードの取得
// 複数レコード取得
rows, err := db.Query(
`SELECT * FROM BOOKS`,
)
if err != nil {
panic(err)
}
// 処理が終わったらカーソルを閉じる
defer rows.Close()
for rows.Next() {
var id int
var title string
// カーソルから値を取得
if err := rows.Scan(&id, &title); err != nil {
log.Fatal("rows.Scan()", err)
return
}
fmt.Printf("id: %d, title: %s\n", id, title)
}
レコードを取得するときは
db.Query(...)
を使う。db.Queryはrowsを返してくれるので、ループでカーソルをずらしながら1件ずつ処理していく。defer rows.Close()
で、処理が終わったときに(遅延させて)カーソルを閉じられる。1件のみ取得
// 1件取得
row := db.QueryRow(
`SELECT * FROM BOOKS WHERE ID=?`,
id,
)
var id int
var title string
err := row.Scan(&id, &title);
// エラー内容による分岐
switch {
case err == sql.ErrNoRows:
fmt.Printf("Not found")
case err != nil:
panic(err)
default:
fmt.Printf("id: %d, title: %s\n", id, title)
}
1件取得するときは
db.QueryRow(...)
を使う。db.QueryRowはRowだけを返すので、取得できなくてもエラーにはならない。エラー処理をする場合は、
row.Scan(...)
をしたときの戻り値errorで判定する。
レコードの更新
// 更新
res, err := db.Exec(
`UPDATE BOOKS SET TITLE=? WHERE ID=?`,
"update title",
id,
)
if err != nil {
panic(err)
}
// 更新されたレコード数
affect, err := res.RowsAffected()
if err != nil {
panic(err)
}
fmt.Printf("affected by update: %d\n", affect)
Insert同様に
db.Exec(...)
を使ってレコードを更新する。また
res.RowsAffected()
により、影響を受けた(更新された)レコード数が取得できる。
レコードの削除
// 削除
res, err := db.Exec(
`DELETE FROM BOOKS WHERE ID=?`,
id,
)
if err != nil {
panic(err)
}
// 削除されたレコード数
affect, err := res.RowsAffected()
if err != nil {
panic(err)
}
fmt.Printf("affected by delete: %d\n", affect)
Insert、Update同様に
db.Exec(...)
を使ってレコードを削除する。
トランザクションを使う
ここまで紹介してきた方法は、すべてトランザクションを使っていないのでロールバックなどできない。
なんらかのサービスを開発するとき、データベースはACIDの原則にもとづいて設計することになる。
途中で失敗したらロールバック、すべて成功したらコミット、のようにACIDにもとづいていないとデータに不整合が起きてしまう。
そこでトランザクションを使う。
// DBのレコードの構造体
type Book struct {
ID int64
Title string
}
// データベース接続
db, err := sql.Open("sqlite3", "./test.db")
if err != nil {
panic(err)
}
// テーブル作成
_, err = db.Exec(
`CREATE TABLE IF NOT EXISTS "BOOKS" ("ID" INTEGER PRIMARY KEY, "TITLE" VARCHAR(255))`,
)
if err != nil {
panic(err)
}
// 処理が終わったらDBのコネクションを閉じる
defer db.Close()
// トランザクション開始
tx, err := db.Begin()
if err != nil {
panic(err)
}
// 10レコード挿入する
stmt, err := tx.Prepare(`INSERT INTO BOOKS (ID, TITLE) VALUES (?, ?)`)
if err != nil {
panic(err)
}
defer stmt.Close()
for i := 0; i < 10; i++ {
id := fmt.Sprintf("%d%d", time.Now().Unix(), i)
// インサート処理の実行
if _, err := stmt.Exec(id, fmt.Sprintf("title-%d", i)); err != nil {
panic(err)
}
}
isOk := true // なんらかの判定
if isOk {
tx.Commit()
} else {
tx.Rollback()
}
// 全レコード取得
rows, err := selectAll(db)
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var b Book
if err := rows.Scan(&b.ID, &b.Title); err != nil {
panic(err)
}
fmt.Printf("id: %d, title: %s\n", b.ID, b.Title)
}
db.Begin()
でトランザクションを開始する。そのときに戻り値Txが返ってくる。あとは必要に応じてtx.Commit()
やtx.Rollback()
を使ってコミット、ロールバックをする。
参考サイト
- sql - The Go Programming Language
- SQLiteデータベースの使用 · Build web application with Golang
- mattn/go-sqlite3: sqlite3 driver for go that using database/sql
以上
written by @bc_rikko
0 件のコメント :
コメントを投稿