Goの標準パッケージを使ってデータベースに接続してSELECT文を実行する方法を調べたので、簡単にまとめる。
準備
1. パッケージのインポート
抽象的な操作を提供する標準パッケージのdatabase/sql
と、実際にMySQLに対する操作が実装されているgo-sql-driver/mysql
をインポートする。他のパッケージは必要に応じて。
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
2. 結果を代入する変数の宣言
var (
id *int
name *string
)
3. sql.DBオブジェクトの生成
使用するドライバやデータベースの認証情報を渡してsql.DB
オブジェクトを生成する。この時点ではまだDBへのコネクションは作られておらず、コネクションは実際にクエリを発行する時にdatabase.sql
パッケージが勝手に生成・破棄してくれるらしい。コネクションプーリングも勝手にやってくれるとのこと。
db, err := sql.Open("mysql", "<HOST>:<PASSWORD>@<PROTOCOL>(<ADDRESS>)/<DATABASE>")
if err != nil {
log.Fatalf("failed to open db: %v", err)
}
defer db.Close()
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
SELECTの実行
以下のデータに対して操作を行う。
mysql> desc experiments;
+------------+-----------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------+------+-----+-------------------+-------------------+
| id | int | NO | PRI | NULL | auto_increment |
| network_id | int | NO | MUL | NULL | |
+------------+-----------+------+-----+-------------------+-------------------+
mysql> select id, network_id from experiments;
+----+------------+
| id | network_id |
+----+------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
+----+------------+
mysql> desc networks;
+------------+--------------+------+-----+-------------------+-----------------------------------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+-------------------+-----------------------------------------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
+------------+--------------+------+-----+-------------------+-----------------------------------------------+
mysql> select id, name from networks;
+----+------+
| id | name |
+----+------+
| 1 | tcp |
| 2 | quic |
+----+------+
複数行 / 単発
*sql.DB.Query()
の第一引数にクエリ、第二引数以降にプレースホルダに入れる引数を渡す(もしあれば)と結果のイテレーターが返ってくるので、Next()メソッドで順に結果を取得することができる。
rows, err := db.Query(
"select e.id from experiments e join networks n on e.network_id = n.id where n.name = ?",
"tcp",
)
if err != nil {
log.Fatalf("failed to query: %v", err)
}
defer rows.Close()
for rows.Next() {
if err := rows.Scan(&id); err != nil {
log.Fatalf("failed to scan row: %s", err)
}
fmt.Printf("id: %d\n", *id)
}
if err = rows.Err(); err != nil {
log.Fatalln(err)
}
結果
id: 1
id: 2
複数行 / 複数発
*sql.DB.Prepare()
にプレースホルダ付きのクエリを渡して*sql.Stmt
を得る。*sql.Stmt.Query()
にプレースホルダに入れる値を渡すと*sql.DB.Query()
と同様に結果のイテレーションが返ってくる。パラメータを変えて何度か同じクエリを実行したい際に使う。
stmt, err := db.Prepare("select e.id from experiments e join networks n on e.network_id = n.id where n.name = ?")
if err != nil {
log.Fatalf("failed to prepare: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query("tcp")
if err != nil {
log.Fatalf("failed to query: %v", err)
}
defer rows.Close()
fmt.Println("case tcp")
for rows.Next() {
if err := rows.Scan(&id); err != nil {
log.Fatalf("failed to scan row: %s", err)
}
fmt.Printf("id: %d\n", *id)
}
if err = rows.Err(); err != nil {
log.Fatalln(err)
}
fmt.Println("case quic")
rows, err = stmt.Query("quic")
if err != nil {
log.Fatalf("failed to query: %v", err)
}
defer rows.Close()
for rows.Next() {
if err := rows.Scan(&id); err != nil {
log.Fatalf("failed to scan row: %s", err)
}
fmt.Printf("id: %d\n", *id)
}
if err = rows.Err(); err != nil {
log.Fatalln(err)
}
結果
case tcp
id: 1
id: 2
case quic
id: 3
単一行
単一行だけ取得する際の便利メソッドとして*sql.DB.QueryRow()
がある。呼び出しは*sql.DB.Query()
と同じように第一引数にクエリ、第二引数にプレースホルダーに入れるパラメータを渡す。*sql.DB.QueryRow()
からは*Row
オブジェクトが返ってくるので、メソッドチェーンでScan()
を呼ぶことでわざわざ単一行の結果に対してイテレーションを回さなくて済む。
if err := db.QueryRow(`select e.id, n.name from experiments e
join networks n on e.network_id = n.id
where n.name = ?
order by e.created_at desc
limit 1`, "tcp",
).Scan(&id, &name); err != nil {
log.Fatalf("failed to query row: %v", err)
}
fmt.Printf("id: %d, name: %s\n", *id, *name)
結果
id: 2, name: tcp