【Go言語】database/sqlパッケージによるデータベース操作入門 – sqlite3

目次

はじめに

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リクエストを送信する

よかったらシェアしてね!

コメント

コメントする

CAPTCHA


目次
閉じる