はじめに
本記事で分かること
Golangを活用したデータベースとの連携方法について、サンプルコードを交えて解説します。
- 利用するパッケージ
- データベースへの接続方法
- クエリ実行方法
- トランザクション処理
前提条件
- Golangのインストールが完了していること
- PostgreSQLのインストールが完了していること
上記作業が完了していない方は、以下の記事でインストール作業を実施してください。

利用するパッケージ
サンプルソースを解説する前に、事前知識としてデータベース連携に必要なパッケージを解説します。
- database/sql
- github.com/lib/pq
database/sql
- 概要
-
- Golangの標準ライブラリに含まれる、データベースへのアクセスを提供するパッケージです。
- データベースの種類に依存しない共通のインターフェースを提供し、個別のデータベースドライバと組み合わせて使用します。
- 主な機能
-
- データベースへの接続と切断
- SQLクエリの実行
- トランザクション処理
- 結果セットの取得
github.com/lib/pq
- 概要
-
- PostgreSQL用のデータベースドライバ。
- アプリケーションとデータベース管理システム(DBMS)との間の橋渡し役を担うソフトウェア。
- PostgreSQL用のデータベースドライバ。
- 主な機能
-
- 接続の維持、切断、プール管理など、接続に関する様々な処理
- アプリケーションから送信されたSQLクエリをデータベースが理解できる形式に変換
①データベースへの接続方法
ここからは、サンプルソースを交えてデータベース連携方法を解説します。
①-1 データベース・テーブル作成
まずは接続対象のデータベース・テーブルを、以下のように作成します。
- データベース名:test_db
- テーブル名:test_table
- カラム1:user_id(Cahacter(10))
- カラム2:user_name(Character(10))
上記データベース・テーブルの作成方法は、以下の記事で解説しています。
①-2 DB接続
ここからは、実際にGolangでソースコードを交えて解説します。
mkdir db-linkage
cd db-linkage
go mod init db-linkage
⇒go.mod
というファイルが生成され、プロジェクトの依存関係を管理できるようになります。
go get github.com/lib/pq
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()
関数は、データベース接続をクローズします。- これにより、関数終了時にデータベース接続が確実にクローズされ、リソースリークを防ぎます。
go run main.go
⇒処理が無事完了したらPostgreSQLに接続成功!
が出力されます。
②クエリ実行方法
次に、データベースに対してクエリを実行する方法を解説します。
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
変数には、直前の処理で発生したエラー情報が格納されています。
go run main.go
⇒処理が無事完了したらクエリ実行成功!
が出力されます。
トランザクション処理
最後に、トランザクション処理として以下の3つを解説します。
- トランザクションの開始
- ロールバック
- コミット
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
が格納されます。
go run main.go
⇒処理が無事完了したらコミット成功!
が出力されます。
コメント