はじめに
database/sqlパッケージを利用したデータベース操作の基本をまとめます。今回はsqlドライバーにmattn/go-sqlite3を用いました。データベースの作成からCRUD処理までの基本操作を説明していきます。
database/sqlパッケージとは
database/sqlパッケージはSQLデータベースの汎用的なインターフェースを提供します。アプリケーションはこのパッケージを通してSQLドライバーを操作し、データベースの処理を行います。このように、アプリケーションとSQLドライバーの中間にdatabase/sqlパッケージが位置することにより、様々なデータベースの差を吸収して汎用化しています。ユーザはdatabase/sqlパッケージを介してデータベースを操作することで、様々なデータベースを同じようなの操作で扱うことができます。ただ、細かい違いはあるのでご注意ください。
database/sqlパッケージに対応するSQLドライバーはこちらです。MySQL、ORACLE、Postgres、SQLiteをはじめとし、様々はSQLドライバーが対応しています。
事前準備
※Macに絞った事前準備になります。
SQLite3 導入
ターミナル上で下記コマンドを実行することでSqlite3を導入できます。
brew install sqlite
gcc 導入
go-sqlite3はcgoパッケージであるため、go-sqlite3を使用してアプリをビルドする場合は、gccが必要です。
gccをインストールするには、Xcodeをダウンロードしてインストール後、下記コマンドを実行してください。
xcode-select --install
mattn/go-sqlite3 導入
ターミナル上で下記コマンドを実行することでmattn/go-sqlite3を導入できます。
go get github.com/mattn/go-sqlite3
データベースの操作方法
データベースに接続する
データベースへの接続にはfunc Open(driverName, dataSourceName string) (*DB, error)
メソッドを使います。第一引数にドライバ名、第二引数にドライバ固有のデータソース名を指定します。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
接続の確認
データベースへの接続確認にはfunc (db *DB) Ping() error
メソッドを使います。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.Ping()
if err != nil {
log.Fatal(err)
}
}
テーブルの作成
sql.Open
メソッドでデータベースに接続した後、func (db *DB) Exec(query string, args …interface{}) (Result, error)
メソッドを用いてSQLのCREATE TABLE
命令を実行します。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
tableName := "user"
cmd := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id SERIAL PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
age INTEGER NOT NULL,
email TEXT)`, tableName)
_, err = db.Exec(cmd)
if err != nil {
log.Fatal(err)
}
}
ターミナルで下記コマンドを実行し、作成したテーブルが表示されれば成功です。
sqlite> .open example.sql
sqlite> .tables
user
データの挿入
方法1:func (db *DB) Exec(query string, args …interface{}) (Result, error)
テーブルの作成で用いたfunc (db *DB) Exec(query string, args …interface{}) (Result, error)
メソッドを用いて命令を実行する方法です。挿入するSQL命令INSERT INTO ...
をExec
メソッドで実行します。
成功するとExecメソッドからResultインターフェース型の変数が返却されます。ResultインターフェースにはLastInsertId() (int64, error)
メソッドとRowsAffected() (int64, error)
メソッドが実装されており、前者は最後に追加・変更したIdの値を返却し、後者は影響を与えたレコード数を返却します。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
cmd := "INSERT INTO user (id, name, age, email) VALUES ($1, $2, $3, $4)"
result, err := db.Exec(cmd, 1, "Taro", 20, "xxxxxx@yyyyyy.com")
if err != nil {
log.Fatal(err)
}
id, err := result.LastInsertId()
if err != nil {
log.Fatal(err)
}
fmt.Printf("LastInsertId: %d\n", id)
num, err := result.RowsAffected()
if err != nil {
log.Fatal(err)
}
fmt.Printf("RowsAffected: %d\n", num)
}
LastInsertId: 1
RowsAffected: 1
方法2:func (db *DB) Prepare(query string) (*Stmt, error)
Prepared Statementを使用する方法です。func (db *DB) Prepare(query string) (*Stmt, error)
メソッドを使用してINSERT INTO ...
命令を準備し、戻り値である*Stmtが有するfunc (s *Stmt) Exec(args …interface{}) (Result, error)
メソッドで命令を実行します。
方法1では命令の実行ごとにコネクションの確保と解放を行うのに対し、方法2ではStmtを解放するまでコネクションを保持している点が違いの一つとなります。そのため、複数の命令を実行する場合はPrepared Statementを利用して実行する方法が効率的です。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
stmt, err := db.Prepare("INSERT INTO user (id, name, age, email) VALUES (?, ?, ?, ?)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
if _, err = stmt.Exec(1, "Taro", 20, "xxxxxx@yyyyyy.com"); err != nil {
log.Fatal(err)
}
}
データの読み込み
複数レコード
複数レコードを読み込む場合にはfunc(db * DB)Query(query string、args … interface {})(* Rows、error)
メソッドを利用して、SELECT
命令を実行します。読み込まれたレコードは戻り値Rowsに格納されて返却されます。
Rowsから一レコードずつ取り出す場合は、func (rs *Rows) Next() bool
メソッドでカーソルを次の行へ進めながら、func (rs *Rows) Scan(dest …interface{}) error
メソッドを用いてdest引数で指定する変数へと格納していきます。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
cmd := "SELECT * FROM user WHERE Age >= $1"
rows, err := db.Query(cmd, 18)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
var age int
var email sql.NullString
err := rows.Scan(&id, &name, &age, &email)
if err != nil {
log.Fatal(err)
}
fmt.Println(id, name, age, email.String)
}
}
最初の1レコードのみ
最初の1レコードのみを取得する場合、func (db *DB) QueryRow(query string, args …interface{}) *Row
メソッドを使用します。SQL実行方法およびレコード内容の取得方法は複数レコードの場合と同様です。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
cmd := "SELECT * FROM user WHERE Age >= $1"
row := db.QueryRow(cmd, 18)
var id int
var name string
var age int
var email sql.NullString
err = row.Scan(&id, &name, &age, &email)
if err != nil {
log.Fatal(err)
}
fmt.Println(id, name, age, email.String)
}
データの更新
func (db *DB) Exec(query string, args …interface{}) (Result, error)
メソッドを用いてUPDATE
命令を実行します。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
cmd := "UPDATE user SET age = $1 WHERE name = $2"
_, err = db.Exec(cmd, 21, "Taro")
if err != nil {
log.Fatal(err)
}
}
データの削除
func (db *DB) Exec(query string, args …interface{}) (Result, error)
メソッドを用いてDELETE
命令を実行します。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
cmd := "DELETE FROM user WHERE name = $1"
_, err = db.Exec(cmd, "Taro")
if err != nil {
log.Fatal(err)
}
}
トランザクション
最初にfunc (db DB) Begin() (Tx, error)
メソッドでトランザクションを開始します。
そして、func (tx *Tx) Exec(query string, args …interface{}) (Result, error)
メソッドを用いてSQL命令を実行するか、func (tx Tx) Prepare(query string) (Stmt, error)
メソッドでSQL命令を準備した後にfunc (s *Stmt) Exec(args …interface{}) (Result, error)
メソッドで準備したSQL命令を実行します。
最後にfunc (tx *Tx) Commit() error
メソッドでトランザクションを終了します。このCommit()
メソッドが実行されることにより、それまでに実行されたSQLがデータベースに反映されます。もし、Commit()
メソッドが実行される前にエラーが発生した場合は、全てのSQLが反映されず、ロールバックされます。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
users := []struct {
id int
name string
age int
email string
}{
{id: 1, name: "Taro", age: 20, email: "xxxxx@yyyyyy.com"},
{id: 2, name: "Jiro", age: 18, email: "yyyyy@yyyyyy.com"},
{id: 3, name: "Saburo", age: 15, email: "zzzzz@yyyyyy.com"},
}
db, err := sql.Open("sqlite3", "./example.sql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
stmt, err := tx.Prepare("INSERT INTO user (id, name, age, email) VALUES ($1, $2, $3, $4)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
for _, v := range users {
_, err := stmt.Exec(v.id, v.name, v.age, v.email)
if err != nil {
log.Fatal(err)
}
}
tx.Commit()
}
まとめ
本記事ではdatabase/sqlパッケージとsqlドライバーmattn/go-sqlite3を利用して、データベースの作成からCRUD処理までの基本操作方法まとめました。
閲覧いただきありがとうございました。
他のパッケージについても記事を書いていますので、もし興味がありましたらご参照ください。
・【Go言語】encoding/jsonパッケージでJSONをパースする
・【Go言語】net/httpパッケージでAPIリクエストを送信する
コメント