データベースハンドルのオープン

database/sqlパッケージは、接続管理の必要性を減らすことで、データベースアクセスを簡素化します。多くのデータアクセスAPIとは異なり、database/sqlでは、明示的に接続を開き、作業を行い、接続を閉じることはありません。代わりに、コードは接続プールを表すデータベースハンドルを開き、ハンドルでデータアクセス操作を実行します。取得した行や準備されたステートメントが保持するリソースなど、リソースを解放する必要がある場合にのみCloseメソッドを呼び出します。

言い換えれば、sql.DBで表されるデータベースハンドルが接続を処理し、コードに代わって接続を開閉します。コードがハンドルを使用してデータベース操作を実行すると、これらの操作はデータベースに同時にアクセスします。詳細については、接続の管理を参照してください。

注:データベース接続を予約することもできます。詳細については、専用接続の使用を参照してください。

database/sqlパッケージで利用できるAPIに加えて、Goコミュニティは最も一般的な(そして多くの珍しい)データベース管理システム(DBMS)用のドライバーを開発しています。

データベースハンドルを開く際には、次の上位レベルのステップに従います。

  1. ドライバーを見つける。

    ドライバーは、Goコードとデータベース間のリクエストとレスポンスを変換します。詳細については、データベースドライバーの特定とインポートを参照してください。

  2. データベースハンドルを開く。

    ドライバーをインポートした後、特定のデータベースのハンドルを開くことができます。詳細については、データベースハンドルのオープンを参照してください。

  3. 接続を確認する。

    データベースハンドルを開いたら、コードは接続が利用可能であることを確認できます。詳細については、接続の確認を参照してください。

通常、コードはデータベース接続を明示的に開いたり閉じたりしません。それはデータベースハンドルによって行われます。ただし、コードは、クエリ結果を含むsql.Rowsなど、途中で取得したリソースを解放する必要があります。詳細については、リソースの解放を参照してください。

データベースドライバーの特定とインポート

使用しているDBMSをサポートするデータベースドライバーが必要です。データベースのドライバーを見つけるには、SQLDriversを参照してください。

ドライバーをコードで利用できるようにするには、他のGoパッケージと同様にインポートします。以下に例を示します。

import "github.com/go-sql-driver/mysql"

ドライバーパッケージから直接関数を呼び出していない場合(sqlパッケージによって暗黙的に使用されている場合など)は、インポートパスの前にアンダースコアを付ける空白インポートを使用する必要があります。

import _ "github.com/go-sql-driver/mysql"

注:ベストプラクティスとして、データベース操作にデータベースドライバー独自のAPIを使用することは避けてください。代わりに、database/sqlパッケージの関数を使用してください。これにより、コードとDBMSの結合度が低く保たれ、必要に応じて別のDBMSに切り替えるのが容易になります。

データベースハンドルのオープン

sql.DBデータベースハンドルは、個別に、またはトランザクション内で、データベースからの読み取りと書き込みの機能を提供します。

データベースハンドルは、sql.Open(接続文字列を受け取る)またはsql.OpenDBdriver.Connectorを受け取る)のいずれかを呼び出すことで取得できます。どちらもsql.DBへのポインタを返します。

注:データベースの認証情報をGoのソースコードに含めないでください。詳細については、データベース認証情報の保存を参照してください。

接続文字列によるオープン

接続文字列を使用して接続する場合は、sql.Open関数を使用します。文字列の形式は、使用しているドライバーによって異なります。

MySQLの例を以下に示します。

db, err = sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords")
if err != nil {
    log.Fatal(err)
}

ただし、接続プロパティをより構造化された方法でキャプチャする方が、より読みやすいコードになることがわかるでしょう。詳細はドライバーによって異なります。

たとえば、前の例を次のように置き換えることができます。これは、MySQLドライバーのConfigを使用してプロパティを指定し、そのFormatDSNメソッドを使用して接続文字列を構築します。

// Specify connection properties.
cfg := mysql.NewConfig()
cfg.User = username
cfg.Passwd = password
cfg.Net = "tcp"
cfg.Addr = "127.0.0.1:3306"
cfg.DBName = "jazzrecords"

// Get a database handle.
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
    log.Fatal(err)
}

Connectorによるオープン

接続文字列では利用できないドライバー固有の接続機能を利用したい場合は、sql.OpenDB関数を使用します。各ドライバーは独自の接続プロパティのセットをサポートしており、多くの場合、DBMSに固有の接続リクエストをカスタマイズする方法を提供しています。

前のsql.Openの例をsql.OpenDBを使用するように変更すると、次のようなコードでハンドルを作成できます。

// Specify connection properties.
cfg := mysql.NewConfig()
cfg.User = username
cfg.Passwd = password
cfg.Net = "tcp"
cfg.Addr = "127.0.0.1:3306"
cfg.DBName = "jazzrecords"

// Get a driver-specific connector.
connector, err := mysql.NewConnector(&cfg)
if err != nil {
    log.Fatal(err)
}

// Get a database handle.
db = sql.OpenDB(connector)

エラーの処理

コードは、sql.Openなど、ハンドル作成の試行からエラーをチェックする必要があります。これは接続エラーではありません。代わりに、sql.Openがハンドルを初期化できなかった場合にエラーが発生します。これは、たとえば、指定したDSNを解析できなかった場合に発生する可能性があります。

接続の確認

データベースハンドルを開いても、sqlパッケージがすぐに新しいデータベース接続を作成しない場合があります。代わりに、コードが必要なときに接続を作成する可能性があります。すぐにデータベースを使用せず、接続が確立できることを確認したい場合は、PingまたはPingContextを呼び出します。

次の例のコードは、データベースをpingして接続を確認します。

db, err = sql.Open("mysql", connString)

// Confirm a successful connection.
if err := db.Ping(); err != nil {
    log.Fatal(err)
}

データベース認証情報の保存

Goのソースコードにデータベースの認証情報を保存することは避けてください。データベースの内容が他人に公開される可能性があります。代わりに、コードの外で、しかし利用可能な場所に保存する方法を見つけてください。たとえば、認証情報を保存し、コードがDBMSの認証情報を取得するために使用できるAPIを提供するシークレットキーパーアプリを検討してください。

一般的なアプローチの1つは、プログラムが開始する前に環境にシークレットを保存することです。おそらくシークレットマネージャーからロードされ、Goプログラムはos.Getenvを使用してそれらを読み取ることができます。

username := os.Getenv("DB_USER")
password := os.Getenv("DB_PASS")

このアプローチにより、ローカルテスト用に環境変数を自分で設定することもできます。

リソースの解放

database/sqlパッケージでは接続を明示的に管理またはクローズしませんが、コードは、不要になったときに取得したリソースを解放する必要があります。これには、クエリから返されたデータを表すsql.Rows、または準備されたステートメントを表すsql.Stmtが保持するリソースが含まれます。

通常、リソースはClose関数の呼び出しを遅延させることでクローズされ、囲んでいる関数が終了する前にリソースが解放されます。

次の例のコードは、sql.Rowsが保持するリソースを解放するためにCloseを遅延させます。

rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

// Loop through returned rows.