データベースの利用
Goを使ってデータベースにアクセスする。SQLiteというデータベースのシステムを利用するために、go-sqlite3というパッケージを使う。
データベースの操作
データベースはSQLで操作を行う。主なSQL文の種類としては以下。
CREATE文:テーブルを作る
INSERT文:レコードを追加する
SELECT文:レコードを取得する
UPDATE文:レコードを更新する
DELETE文:レコードを削除する
SQLでデータベースを操作するには、database/sqlパッケージを使用する。データベースを操作する大まかな流れは次の通り。
①sql.Open関数でデータベースを開く
②SQL文を作る
③SQL文を実行する
④Closeメソッドでデータベースを閉じる
まず、インストールしたgithub.com/mattn/go-sqlite3を、_を使ってインポートする。
sqlite3はコードの中で使わないが、ビルド時に必要なため。
また、「var DbConnection *sql.DB」とグローバルで宣言し、データベースを利用する準備をする。
(補足)
この変数宣言は、DbConnection、という名前で「*sql.DB」型の変数を宣言しているという意味。
sql.DB 型について、
パッケージ:database/sql パッケージに含まれている。
役割:データベースへの接続を管理し、クエリの実行やトランザクションの開始など、データベース操作のためのメソッドを提供する。
接続プール:sql.DB は内部的に接続プールを管理しており、接続の再利用や新規作成を自動的に行う。
ポインタを使う理由は、以下。
共有アクセス:ポインタを使うことで、複数の関数やメソッドから同じデータベース接続を共有できるため。
効率性:データベース接続はリソースを消費するため、一度確立した接続を再利用する方が効率的だから。
可変性:ポインタを使うと、接続先を変更したい場合などに変数自体を再代入できる。
(補足終わり)
続いてsql.Open関数でデータベースを開く。引数には「sqlite3」というデーベースのドライバ名と、「./example.sql」というファイル名を指定して、返り値をDbConnectionに代入する。この変数DbConnectionを使って、データベースを操作していく。また、最後にデータベースは閉じる必要があるので、deferで「Db.Connection.Close()」を実行する。
次に、テーブルを作成するSQL文であるCREATE文を作成する。
CREATE TABLE テーブル名 (カラム名 型, ~)
テーブルが存在しない場合のみ作成したい場合は次のようになる。
CREATE TABLE IF NOT EXISTS テーブル名 (カラム名 型, ~)
ここでは、「person」という名前のテーブルが存在しないときに「person」といいうテーブルを作成し、string型のnameとint型のageというカラムを作る。「`」で囲むことで、開業しながらSQL文を書くことができる。
また、SQL文は変数cmdに代入する。代入したら、Execメソッドを使用して「Db.Connection.Exec(cmd)」のように実行する。
CREATE文の時は返り値を使用しないので、エラーのみを受け取ってエラーハンドリングを行う。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name STRING
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
}
上記のコードを実行するとディレクトリ内に「example.sql」というファイルが作成される。
ターミナルから「sqlite3 example.sql」を実行して対話型シェルを起動させる。「.table」というコマンドを実行すると、「person」テーブルができていることを確認できる。
INSERT文でレコードを挿入する
INSERT文の書き方は以下。
INSERT INTO テーブル名 (カラム名, ~) VALUES (追加する値, ~)
このとき、追加する値を「?」にしておくと、Execメソッドの実行時に値を渡すことができる。この「?」をプレースホルダという。
次のコードを実行する。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "INSERT INTO person (name, age) VALUES(?, ?)"
_, err = DbConnection.Exec(cmd, "Nancy", 20)
if err != nil {
log.Fatalln(err)
}
}
実行後sqlite3でテーブルの中身をselect文で確認する。
続いて、コードを書き換えて「Mike」「24」のレコードも追加する。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "INSERT INTO person (name, age) VALUES(?, ?)"
_, err = DbConnection.Exec(cmd, "Mike", 24)
if err != nil {
log.Fatalln(err)
}
}
UPDATE文でのレコード更新
UPDATE文の書き方は以下。更新後の値や更新箇所の条件を指定する。
UPDATE テーブル名 SET カラム名 = ~ WHERE = ~
今回はMikeのageを25に更新するために以下のコードを実行する。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "UPDATE person SET age = ? WHERE name = ? "
_, err = DbConnection.Exec(cmd, 25, "Mike")
if err != nil {
log.Fatalln(err)
}
}
SELECT文で複数のレコードを取得する
INSERT文をもう一度実行し、「Nancy」「20」のレコードをもう一件挿入する。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "INSERT INTO person (name, age) VALUES (?, ?) "
_, err = DbConnection.Exec(cmd, "Nancy", 20)
if err != nil {
log.Fatalln(err)
}
}
実行結果は以下。
この状態で、「personテーブルのすべてのレコードを取得する」というSELECT文を作って実行する。
SELECT文で取得したレコードを得るためには、Queryメソッドを使う。なお、ここではエラーハンドリングをしない。また、Queryメソッドの返り値を代入した変数rowは、deferで「rows.Close()」を実行する必要がある。
次に、読み込んだレコードのデータを格納するための構造体Personを作成し、NameとAgeのフィールドを作る。「var pp []Person」と構造体Personのスライスを作り、ここにPersonテーブルのレコードを1件ずつ格納する。
変数rowsでNextメソッドを使い、forループで1件ずつレコードを取り出していく。ループの中で変数pを宣言した後、Scanメソッドで「rows.Scan(&p.Name, &p.Age)」として変数pのフィールドに値を入れて、変数ppのスライスにappend関数で追加する。
その後、rangeで変数ppから1つずつ値を取り出してNameとAgeを表示してみる。
次のコードを実行すると、personテーブルのすべてのレコードが出力される。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
type Person struct {
Name string
Age int
}
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "SELECT * FROM person"
rows, _ := DbConnection.Query(cmd)
defer rows.Close()
var pp []Person
for rows.Next() {
var p Person
err := rows.Scan(&p.Name, &p.Age)
if err != nil {
log.Println(err)
}
pp = append(pp, p)
}
for _, p := range pp {
fmt.Println(p.Name, p.Age)
}
}
SELECT文で1件のレコードを取得する
SELECT文で条件を指定する場合は、WHERE句のあとに条件を書く。「SELECT * FROM person where age = ?」とした場合、personテーブルからageカラムの値が「?」で指定した値と等しいレコードのみを取得できる。
レコードを1件だけ取得したいQueryRowメソッドを使用する。取得したレコードは、Scanメソッドで変数pに値を格納する。QueryRowメソッドではエラーハンドリングを少し変更する。エラーを格納した変数errがsql.ErrNoRows(当てはまるレコードがないときに起こるエラー)の場合は「log.Println("NO row")」を実行し、そうでない場合は、「log.Println(err)」を実行する。
次のコードを実行すると、ageカラムの値が「20」のレコードが取得される。
personテーブルには、ageカラムの値が「20」のレコードが2件存在するが、QueryRowメソッドはレコードを1件のみ取得する。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
type Person struct {
Name string
Age int
}
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "SELECT * FROM person where age = ?"
row := DbConnection.QueryRow(cmd, 20)
var p Person
err = row.Scan(&p.Name, &p.Age)
if err != nil {
if err == sql.ErrNoRows {
log.Println("No row")
} else {
log.Println(err)
}
}
fmt.Println(p.Name, p.Age)
}
実行結果は以下。
また、WHERE句の条件に「1000」を指定してみる。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
type Person struct {
Name string
Age int
}
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "SELECT * FROM person where age = ?"
row := DbConnection.QueryRow(cmd, 1000)
var p Person
err = row.Scan(&p.Name, &p.Age)
if err != nil {
if err == sql.ErrNoRows {
log.Println("No row")
} else {
log.Println(err)
}
}
fmt.Println(p.Name, p.Age)
}
当てはまるレコードがpersonテーブルに存在しないため、「No row」のログが出力されている。
また、最後の「fmt.Println(p.Name, p.Age)」の処理では、変数pに値が入っていないため、それぞれNameとAgeのデフォルト値(空文字と0)が表示されている。
DELETE文でレコードを削除する
レコードを削除する場合はDELETE文を実行する。ここでは、「DELETE FROM person WHERE name = ?」として、personテーブルからnameカラムの値が「?」で指定した値と等しいレコードを削除する。Execメソッドで、「?」に「Nancy」を指定してSQL文を実行する。
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DbConnection *sql.DB
type Person struct {
Name string
Age int
}
func main() {
DbConnection, _ := sql.Open("sqlite3", "example.sql")
defer DbConnection.Close()
cmd := `CREATE TABLE IF NOT EXISTS person (
name TEXT,
age INT
)`
_, err := DbConnection.Exec(cmd)
if err != nil {
log.Fatalln(err)
}
cmd = "DELETE FROM person WHERE name = ?"
_, err = DbConnection.Exec(cmd, "Nancy")
if err != nil {
log.Fatalln(err)
}
}
コードを実行した後のテーブルの確認結果は以下で「Nancy」のレコードが消えている。
学習に使用した教材
・『入門】Golang基礎入門 + 各種ライブラリ + 簡単なTodoWebアプリケーション開発(Go言語)』M.A EduTech
https://www.udemy.com/course/golang-webgosql/?utm_medium=udemyads&utm_source=bene-msa&utm_campaign=responsive&utm_content=top-1&utm_term=general&msclkid=81e2f24a32cc185d275d953d60760226&couponCode=NEWYEARCAREERJP
・『シリコンバレー一流プログラマーが教える Goプロフェッショナル大全』酒井 潤 (著)
https://www.amazon.co.jp/%E3%82%B7%E3%83%AA%E3%82%B3%E3%83%B3%E3%83%90%E3%83%AC%E3%83%BC%E4%B8%80%E6%B5%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E6%95%99%E3%81%88%E3%82%8B-Go%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E5%A4%A7%E5%85%A8-%E9%85%92%E4%BA%95-%E6%BD%A4/dp/4046070897