Go言語の公式のDB処理チュートリアルを元にしていますが、DBをMySQLからSqlite3に変更、データの追加にトランザクションを利用するように変更しています。
構成
- Sqlite3 3.36.0
- go 1.17
- github.com/mattn/go-sqlite3 v1.14.11
DDLとDML
※Sqlite3の使い方については本件の本質ではないので割愛します。
DDL
CREATE TABLE album (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(128) NOT NULL,
artist VARCHAR(255) NOT NULL,
price DECIMAL(5,2) NOT NULL
);
DML
INSERT INTO album
(id, title, artist, price)
VALUES
(1, 'Blue Train', 'John Coltrane', 56.99);
INSERT INTO album
(id, title, artist, price)
VALUES
(2, 'Giant Steps', 'John Coltrane', 63.99);
INSERT INTO album
(id, title, artist, price)
VALUES
(3, 'Jeru', 'Gerry Mulligan', 17.99);
INSERT INTO album
(id, title, artist, price)
VALUES
(4, 'Sarah Vaughan', 'Sarah Vaughan', 34.98);
DB接続
database/sqlパッケージには、DBの接続、操作、トランザクションなどのRDBMSの基本的処理をするタイプと関数が含まれています。
あとは、DB接続は使いたいDBのドライバーをimportすれば、接続時のコンフィグレーション以外は実装に差異はほぼありません。
package main
import (
"database/sql" // DB用汎用インターフェース
"fmt"
"log"
_ "github.com/mattn/go-sqlite3" // SQLite3ドライバーのパッケージ
)
// DBの接続
var db *sql.DB // コード単純化をするために、グローバル変数にする
func main(){
// DBの接続
var err error
db, err = sql.Open("sqlite3", "./recordings.db")
if err != nil {
log.Fatal(err)
}
defer db.Close() // 関数終了時に自動でリソースを解放する
// 接続の確認
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connectied!")
}
テーブルの検索
// IDからAlbumを取得する
func albumByID(id int64) (Album, error) {
var album Album
// SQLインジェクジョン回避のためにプレペアードステートメントを生成する
stmt, err := db.Prepare("SELECT * FROM album where id = ?")
if err != nil {
return album, fmt.Errorf("albumByID %q: %v", id, err)
}
defer stmt.Close()
// プレペアードステートメントのプレースホルダに変数を設定してクエリを実行する
rows := stmt.QueryRow(id)
if err := rows.Scan(&album.ID, &album.Title, &album.Artist, &album.Price); err != nil {
if err == sql.ErrNoRows {
return album, fmt.Errorf("albumByID %q: %v", id, err)
}
return album, err
}
return album, nil
}
テーブルにレコードの追加
// albumを追加する
func addAlbums(albums []Album) ([]int64, error) {
// トランザクション開始
tx, err := db.Begin()
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
defer tx.Rollback() // 関数終了時に自動でロールバックする
// プレペアステートメントの生成
stmt, err := tx.Prepare("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)")
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
defer stmt.Close() // 関数終了時に自動でリソースを解放する
var indexs []int64
for _, album := range albums {
// プレースホルダに値を設定してクエリを実行する
result, err := stmt.Exec(album.Title, album.Artist, album.Price)
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
id, err := result.LastInsertId()
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
indexs = append(indexs, id)
}
// コミット
if err = tx.Commit(); err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
return indexs, nil
}
完成したコード
package main
import (
"database/sql" // DB用汎用インターフェース
"fmt"
"log"
_ "github.com/mattn/go-sqlite3" // SQLite3ドライバーのパッケージ
)
// DBの接続
var db *sql.DB
// Albumテーブルのスキーマ構造体
type Album struct {
ID int64
Title string
Artist string
Price float32
}
// IDからAlbumを取得する
func albumByID(id int64) (Album, error) {
var album Album
// SQLインジェクジョン回避のためにプレペアードステートメントを生成する
stmt, err := db.Prepare("SELECT * FROM album where id = ?")
if err != nil {
return album, fmt.Errorf("albumByID %q: %v", id, err)
}
defer stmt.Close()
// プレペアードステートメントのプレースホルダに変数を設定してクエリを実行する
rows := stmt.QueryRow(id)
if err := rows.Scan(&album.ID, &album.Title, &album.Artist, &album.Price); err != nil {
if err == sql.ErrNoRows {
return album, fmt.Errorf("albumByID %q: %v", id, err)
}
return album, err
}
return album, nil
}
// アーティストからAlbumを取得する
func albumsByArtist(name string) ([]Album, error) {
var albums []Album
// DBの検索する
rows, err := db.Query("SELECT * FROM album where artist = ?", name)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
defer rows.Close() // 関数終了時に自動でリソース解放する
for rows.Next() {
var alb Album
if err := rows.Scan(
&alb.ID,
&alb.Title,
&alb.Artist,
&alb.Price); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
albums = append(albums, alb)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
return albums, nil
}
// albumを追加する
func addAlbum(alb Album) (int64, error) {
result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
return id, nil
}
// albumを追加する
func addAlbums(albums []Album) ([]int64, error) {
// トランザクション開始
tx, err := db.Begin()
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
defer tx.Rollback() // 関数終了時に自動でロールバックする
// プレペアステートメントの生成
stmt, err := tx.Prepare("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)")
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
defer stmt.Close() // 関数終了時に自動でリソースを解放する
var indexs []int64
for _, album := range albums {
// プレースホルダに値を設定してクエリを実行する
result, err := stmt.Exec(album.Title, album.Artist, album.Price)
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
id, err := result.LastInsertId()
if err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
indexs = append(indexs, id)
}
// コミット
if err = tx.Commit(); err != nil {
return nil, fmt.Errorf("addAlbums %v: %v", albums, err)
}
return indexs, nil
}
func main() {
// DBの接続
var err error
db, err = sql.Open("sqlite3", "./recordings.db")
if err != nil {
log.Fatal(err)
}
defer db.Close() // 関数終了時に自動でリソースを解放する
// 接続の確認
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connectied!")
albums, err := albumsByArtist("John Coltrane")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)
album, err := albumByID(2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Album found: %v\n", album)
albID, err := addAlbum(Album{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)
albIDs, err := addAlbums([]Album{
{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
},
{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID of added albums: %v\n", albIDs)
}