1
0

More than 1 year has passed since last update.

Go言語のDB接続処理

Posted at

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)

}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0