【Golang】データベース接続・クエリ実行・トランザクション処理の解説【サンプルソース】

目次

はじめに

本記事で分かること

Golangを活用したデータベースとの連携方法について、サンプルコードを交えて解説します。

解説内容
  • 利用するパッケージ
  • データベースへの接続方法
  • クエリ実行方法
  • トランザクション処理

前提条件

前提条件
  • Golangのインストールが完了していること
  • PostgreSQLのインストールが完了していること

上記作業が完了していない方は、以下の記事でインストール作業を実施してください。

利用するパッケージ

サンプルソースを解説する前に、事前知識としてデータベース連携に必要なパッケージを解説します。

利用パッケージ
  • database/sql
  • github.com/lib/pq

database/sql

概要
  • Golangの標準ライブラリに含まれる、データベースへのアクセスを提供するパッケージです。
  • データベースの種類に依存しない共通のインターフェースを提供し、個別のデータベースドライバと組み合わせて使用します。
主な機能
  • データベースへの接続と切断
  • SQLクエリの実行
  • トランザクション処理
  • 結果セットの取得

github.com/lib/pq

概要
  • PostgreSQL用のデータベースドライバ。
    • アプリケーションとデータベース管理システム(DBMS)との間の橋渡し役を担うソフトウェア。
主な機能
  • 接続の維持、切断、プール管理など、接続に関する様々な処理
  • アプリケーションから送信されたSQLクエリをデータベースが理解できる形式に変換

①データベースへの接続方法

ここからは、サンプルソースを交えてデータベース連携方法を解説します。

①-1 データベース・テーブル作成

まずは接続対象のデータベース・テーブルを、以下のように作成します。

  • データベース名:test_db
  • テーブル名:test_table
    • カラム1:user_id(Cahacter(10))
    • カラム2:user_name(Character(10))

上記データベース・テーブルの作成方法は、以下の記事で解説しています。

①-2 DB接続

ここからは、実際にGolangでソースコードを交えて解説します。

STEP
プロジェクトディレクトリの作成
mkdir db-linkage
cd db-linkage
STEP
モジュールの初期化
go mod init db-linkage

go.mod というファイルが生成され、プロジェクトの依存関係を管理できるようになります。

STEP
外部パッケージのインストール
go get github.com/lib/pq
STEP
main.goの作成

main.goに以下ソースコードを貼りつけます。

package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    _ "github.com/lib/pq"
)

func main() {
    // 環境変数から接続情報を取得
    dbUser := os.Getenv("DB_USER")
    dbPass := os.Getenv("DB_PASS")
    dbName := os.Getenv("DB_NAME")
    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")

    // データベース接続情報
    connStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%s sslmode=disable", dbUser, dbPass, dbName, dbHost, dbPort)

    // データベース接続
    db, err := sql.Open("postgres", connStr)
    if err != nil {
            log.Fatal(err)
    }

   // 接続確認
    if err := db.Ping(); err != nil {
	    log.Fatal(err)
    }
    defer db.Close()

    fmt.Println("PostgreSQLに接続成功!")
}

■ソースコード解説

    // データベース接続情報
    connStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%s sslmode=disable", dbUser, dbPass, dbName, dbHost, dbPort)

fmt.Sprintf(...):

  • fmt.Sprintf関数は、フォーマットされた文字列を生成します。
  • 第一引数にフォーマット文字列、第二引数以降にフォーマット文字列内のプレースホルダーに対応する値を指定します。

"user=%s password=%s dbname=%s host=%s port=%s sslmode=disable":

  • これは接続文字列のフォーマットです。
  • %s は文字列のプレースホルダーであり、後続の引数で置き換えられます。
  • 各パラメータは、PostgreSQLデータベースへの接続に必要な情報を示しています。
    • user: データベースのユーザー名
    • password: データベースのパスワード
    • dbname: 接続するデータベース名
    • host: データベースサーバーのホスト名またはIPアドレス
    • port: データベースサーバーのポート番号
    • sslmode=disable: SSL接続を無効にする設定。開発環境などでSSLが不要な場合は、この設定を使用できます。本番環境では、セキュリティのためSSL接続を有効にすることを推奨します。

dbUser, dbPass, dbName, dbHost, dbPort:

  • これらの変数は、os.Getenv()関数を使用して環境変数から取得されたデータベース接続情報です。
  • fmt.Sprintf関数によって、これらの値がフォーマット文字列内の対応するプレースホルダーに埋め込まれます。
    // データベース接続
    db, err := sql.Open("postgres", connStr)
    if err != nil {
            log.Fatal(err)
    }

db, err := sql.Open("postgres", connStr):

  • sql.Open関数は、データベースへの接続を確立します。
  • 第一引数には、使用するデータベースドライバの名前(”postgres”)を指定します。
  • 第二引数には、接続文字列 (connStr) を指定します。
  • この関数は、sql.DB型のデータベース接続オブジェクトと、エラーオブジェクトを返します。
  • db変数には、データベース接続オブジェクトが格納されます。
  • err変数には、エラーが発生した場合にエラー情報が格納されます。

if err != nil { log.Fatal(err) }:

  • err変数がnilでない場合、つまりエラーが発生した場合に、log.Fatal(err)関数が呼び出されます。
  • log.Fatal関数は、エラーログを出力し、プログラムを終了します。
  • これにより、データベース接続に失敗した場合に、エラーメッセージが表示され、プログラムが異常終了します。
	// 接続確認
	if err := db.Ping(); err != nil {
		log.Fatal(err)
	}

if err := db.Ping(); err != nil { ... }:

  • db.Ping() 関数は、データベースへの接続が正常に機能しているかどうかを確認します。
  • この関数は、データベースサーバーとの間で簡単な通信を行い、接続が確立されているかどうかを確認します。
  • エラーが発生した場合(つまり、接続に問題がある場合)、db.Ping() はエラーオブジェクトを返します。
  • if err := ... は、エラーオブジェクトを変数 err に代入し、その値が nil でないか(つまり、エラーが発生したか)をチェックする短縮形です。

log.Fatal(err):

  • log.Fatal() 関数は、エラーログを出力し、プログラムを終了します。
  • err 変数にエラーオブジェクトが格納されている場合、この関数はエラーメッセージをログに出力します。
  • これにより、データベース接続に失敗した場合に、エラーメッセージが表示され、プログラムが異常終了します。
    defer db.Close()

defer db.Close():

  • deferキーワードは、関数終了時に指定された関数を遅延実行します。
  • db.Close()関数は、データベース接続をクローズします。
  • これにより、関数終了時にデータベース接続が確実にクローズされ、リソースリークを防ぎます。
STEP
main.goの実行
go run main.go

 ⇒処理が無事完了したらPostgreSQLに接続成功!が出力されます。

②クエリ実行方法

次に、データベースに対してクエリを実行する方法を解説します。

STEP
main.goの編集
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {

    // DB接続情報
    dbUser := os.Getenv("DB_USER")
    dbPass := os.Getenv("DB_PASS")
    dbName := os.Getenv("DB_NAME")
    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")

	// データベース接続情報
	connStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%s sslmode=disable", dbUser, dbPass, dbName, dbHost, dbPort)

	// データベース接続
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		log.Fatal(err)
	}

	// 接続確認
	if err := db.Ping(); err != nil {
		log.Fatal(err)
	}

	// INSERT
	insert_sql := "INSERT INTO test_table (user_id, user_name) VALUES ($1, $2), ($3, $4), ($5, $6)"
	_, err = db.Exec(insert_sql, "001", "sato", "002", "tanaka", "003", "takahashi")

    // エラー確認
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("クエリ実行成功!!")

	// データベース切断
	db.Close()
}

■ソースコード解説

	// INSERT
	insert_sql := "INSERT INTO test_table (user_id, user_name) VALUES ($1, $2), ($3, $4), ($5, $6)"
	_, err = db.Exec(insert_sql, "001", "sato", "002", "tanaka", "003", "takahashi")

insert_sql := "INSERT INTO test_table (user_id, user_name) VALUES ($1, $2), ($3, $4), ($5, $6)"

  • insert_sql という変数に、SQLのINSERT文を文字列として代入しています。
  • VALUES ($1, $2), ($3, $4), ($5, $6) は、挿入するデータの値をプレースホルダー($1, $2 など)で指定しています。プレースホルダーは、後続の db.Exec 関数で実際の値を指定するために使用されます。

_, err = db.Exec(insert_sql, "001", "sato", "002", "tanaka", "003", "takahashi")

  • db.Exec 関数は、SQLクエリを実行します。
  • 第1引数 insert_sql には、実行するSQLクエリの文字列を指定します。
  • 第2引数以降には、プレースホルダーに対応する実際の値を指定します。この例では、$1"001"$2"sato"$3"002"$4"tanaka"$5"003"$6"takahashi" がそれぞれ対応します。
  • db.Exec 関数の戻り値は2つあります。1つ目は sql.Result 型の値ですが、ここでは _(ブランク識別子)で受け取っています。これは、戻り値を使用しないことを意味します。2つ目は error 型の値で、クエリの実行中にエラーが発生した場合にエラー情報が格納されます。
  • err 変数には、クエリ実行中に発生したエラーが格納されます。エラーが発生しなかった場合は nil が格納されます。
    // エラー確認
	if err != nil {
		log.Fatal(err)
	}

if err != nil { ... }

  • err != nil は、err 変数が nil (ヌル) ではないかどうかを評価する条件式です。
  • Go言語では、関数がエラーを返す場合、通常、最後の戻り値として error 型の値を返します。エラーが発生しなかった場合は nil が返され、エラーが発生した場合はエラー情報が格納された error 型の値が返されます。
  • したがって、err != nil は、直前の処理でエラーが発生したかどうかをチェックしています。

log.Fatal(err)

  • log.Fatal 関数は、引数として渡されたエラーメッセージを標準エラー出力に出力し、プログラムを終了させます。
  • err 変数には、直前の処理で発生したエラー情報が格納されています。
STEP
main.goの実行
go run main.go

⇒処理が無事完了したらクエリ実行成功!が出力されます。

トランザクション処理

最後に、トランザクション処理として以下の3つを解説します。

解説内容
  • トランザクションの開始
  • ロールバック
  • コミット
STEP
main.goの作成
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {

    // DB接続情報
    dbUser := os.Getenv("DB_USER")
    dbPass := os.Getenv("DB_PASS")
    dbName := os.Getenv("DB_NAME")
    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")

	// データベース接続情報
	connStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%s sslmode=disable", dbUser, dbPass, dbName, dbHost, dbPort)

	// データベース接続
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		log.Fatal(err)
	}

	// 接続確認
	if err := db.Ping(); err != nil {
		log.Fatal(err)
	}

	// トランザクション開始
	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
	}

	// INSERT
	insert_sql := "INSERT INTO test_table (user_id, user_name) VALUES ($1, $2), ($3, $4), ($5, $6)"
	_, err = db.Exec(insert_sql, "001", "sato", "002", "tanaka", "003", "takahashi")
	if err != nil {
		tx.Rollback() // エラーが発生したらロールバック
		log.Fatal(err)
	}

	// トランザクションをコミット
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("コミット成功!!")

	// データベース切断
	db.Close()
}

■ソースコード解説

	// トランザクション開始
	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
	}

このコードは、Go言語でデータベースのトランザクションを開始するための処理です。以下に各部分の解説をします。

tx, err := db.Begin()

  • db.Begin() 関数は、データベースのトランザクションを開始し、sql.Tx 型のトランザクションオブジェクトと error 型の値を返します。
  • tx 変数には、トランザクションオブジェクトが格納されます。このオブジェクトを使用して、トランザクション内で実行するSQLクエリを実行したり、トランザクションをコミットまたはロールバックしたりします。
  • err 変数には、トランザクションの開始中に発生したエラーが格納されます。エラーが発生しなかった場合は nil が格納されます。
	if err != nil {
		tx.Rollback() // エラーが発生したらロールバック
		log.Fatal(err)
	}

tx.Rollback()

  • tx.Rollback() 関数は、トランザクションをロールバックします。ロールバックとは、トランザクション内で行われたすべてのデータベース操作を取り消し、トランザクション開始前の状態に戻すことです。
  • トランザクション内でエラーが発生した場合、データベースが矛盾した状態になるのを防ぐために、必ずロールバックを行う必要があります。
	// トランザクションをコミット
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}

err = tx.Commit()

  • tx.Commit() 関数は、トランザクションをコミットします。コミットとは、トランザクション内で行われたすべてのデータベース操作を確定し、データベースに永続化することです。
  • トランザクション内でエラーが発生しなかった場合、tx.Commit() 関数を呼び出してトランザクションをコミットします。
  • err 変数には、トランザクションのコミット中に発生したエラーが格納されます。エラーが発生しなかった場合は nil が格納されます。
STEP
main.goの実行
go run main.go

 ⇒処理が無事完了したらコミット成功!が出力されます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次