#sqlx
sqlx
は優れたビルトインdatabase/sql
パッケージを拡張したGoのパッケージです。
このドキュメントはsqlx
のGoでの使い方にフォーカスしています。
だから、このドキュメントで使われているSQLがベストプラクティスとは限りません。
また、ここではGo開発環境のセットアップ、GoやSQLの基本的な使い方については扱っていません。
これ以降では標準のerr変数を使用してエラーが返されていますが、簡潔にするためにこれらを無視しています。 実際のプログラムでは絶対にすべてのエラーを確認してください。
#参考資料
GoでSQLを使用する方法についての優れたドキュメントは他にもあります。
Goの使い方を学習したい場合は、以下のドキュメントをお勧めします。
database/sql
インターフェースはsqlx
のサブセットです。
だから、これらのドキュメントにあるdatabase/sql
の使い方に関する情報はsqlx
でも通用します。
#はじめに
sqlx
とデータベースドライバーをインストールする必要があります。
データベースのインストールなどの環境構築を省くために、ここではmattnのsqlite3ドライバをインストールします。
$ go get github.com/jmoiron/sqlx
$ go get github.com/mattn/go-sqlite3
#データベース操作に関連した型
sqlx
はdatabase/sql
と同じような感じになるようにしています。
sqlx
には4つの主要なデータベース操作に関連した型(Handle Types)があります。
sqlx.DB
-sql.DB
に類似しており、データベースの表現です。sqlx.Tx
-sql.Tx
に類似しており、トランザクションの表現です。sqlx.Stmt
-sql.Stmt
に類似しており、準備されたステートメントの表現です。sqlx.NamedStmt
- 名前付きクエリをサポートする準備されたステートメントの表現です。
データベース操作に関連した型はすべて、database/sql
の同等物を埋め込み(Embedding)しています。
つまり、sqlx.DB.Query
を呼び出すとき、sql.DB.Query
と同じコードを呼び出しています。
これにより、既存のコードベースに簡単に導入することができます。
これに加えて、2つのカーソルタイプがあります:
sqlx.Rows
-sql.Rows
に類似しており、Queryx
から返されるカーソルです。sqlx.Row
-sql.Row
に類似しており、QueryRowx
から返される結果です。
データベース操作に関連した型と同様に、sqlx.Rows
はsql.Rows
を埋め込んでいます。
基本的な実装がアクセス不可能だったため、sqlx.Row
はsql.Row
の部分的な再実装であり、標準インターフェースを維持しています。
#データベースへの接続
DB
インスタンスは接続そのものではなく、データベースを抽象化したものです。
これがDBを作成してもエラーが返されず、パニックにならない理由です。
sqlx
は内部にコネクションプールを保持しており、初めて接続が必要になった時に接続を試みます。
下記のように、sqlx.DB
はOpen
を使って作成する、もしくは既存のsql.DB
をNewDb
に渡すことで作成することができます。
var db *sqlx.DB
// ビルトインと全く同じ
db = sqlx.Open("sqlite3", ":memory:")
// 既存のsql.DBを使って作成する(ドライバ名が必要なことに注意)
db = sqlx.NewDb(sql.Open("sqlite3", ":memory:"), "sqlite3")
// 強制的に接続して、正常に動作するかテストする
err = db.Ping()
DBをオープンと接続をまとめて行いたい場合もあるかもしれません。
(例: 初期化フェーズ中に設定の問題を検出する)
Connect
を使うとDBのオープンと接続をまとめて行うことができます。
Connect
は新しいDBをオープンしてPing
を試みます。
エラーが発生した場合にパニックを起こすMustConnect
は、パッケージのモジュールレベルで使用することに適しています。
var err error
// オープンして接続する
db, err = sqlx.Connect("sqlite3", ":memory:")
// オープンして接続し、エラー時にパニックを起こす
db = sqlx.MustConnect("sqlite3", ":memory:")
#クエリ入門
下記のようにsqlx
におけるデータベース操作に関連した型は、データベースへのクエリに関する同じ基本的な関数を実装しています。
Exec(...) (sql.Result, error)
-database/sql
から変更なしQuery(...) (*sql.Rows, error)
-database/sql
から変更なしQueryRow(...) *sql.Row
-database/sql
から変更なし
下記はこれらのビルトイン関数への拡張です。
MustExec() sql.Result
-Exec
の拡張、エラーが発生した場合はpanicしますQueryx(...) (*sqlx.Rows, error)
-Query
の拡張、sqlx.Rows
を返しますQueryRowx(...) *sqlx.Row
-QueryRow
の拡張、sqlx.Row
を返します
下記は加えられた全く新しい関数です。
Get(dest interface{}, ...) error
Select(dest interface{}, ...) error
変更されていないインターフェースの関数から新しい関数へこれらの使い方を説明してきます。
#Exec
Exec
とMustExec
はコネクションプールから接続を取得します。
そして、渡されたクエリをデータベースサーバ上で実行します。
この時、アドホックなクエリ実行をサポートしていないドライバの場合、自動的にプリペアドステートメントが作成されて実行されることがあります。
そして、結果が返される前に接続をコネクションプールに返します。
schema := `CREATE TABLE place (
country text,
city text NULL,
telcode integer);`
// データベースサーバでクエリを実行する
result, err := db.Exec(schema)
// または、エラー時にパニックを起こすMustExecを使うことができます
cityState := `INSERT INTO place (country, telcode) VALUES (?, ?)`
countryCity := `INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)`
db.MustExec(cityState, "Hong Kong", 852)
db.MustExec(cityState, "Singapore", 65)
db.MustExec(countryCity, "South Africa", "Johannesburg", 27)
ResultにはLastInsertId()
とRowsAffected()
という2つの関数があります。
これらはドライバによって利用可能かどうかが異なります。
例えばMySQLではオートインクリメントキーを持つ挿入についてはLastInsertId()
が利用可能です。
一方、PostgreSQLではこの情報はRETURNING
句を使用して通常の行カーソルから取得する必要があります。
#バインド変数(bindvars)
クエリ内のプレースホルダである?
は、内部的にバインド変数(bindvars)と呼ばれます。
データベースサーバに値を送る際には、これらを 常に 使用すべきです。
なぜなら、SQLインジェクション攻撃を防ぐことができるからです。
database/sql
はクエリテキストに対して 何の 検証も行いません。
クエリはエンコードされたパラメータと共にそのままデータベースサーバに送信されます。
ドライバが特別なインターフェースを実装していない限り、クエリは実行前にまずデータベースサーバで準備されます。
また、下記のようにバインド変数はデータベースによって異なります。
- MySQLは上記の
?
構文を使用します。 - PostgreSQLは列挙された
$1
、$2
などのバインド変数構文を使用します。 - SQLiteは
?
と$1
の両方の構文を受け入れます。 - Oracleは
:name
構文を使用します。
他のデータベースは異なる場合があります。
現在のデータベースタイプで実行に適したクエリを取得するために、sqlx.DB.Rebind(string) string
関数を?
バインド変数構文と共に使用できます。
バインド変数に関する一般的な誤解は、それらが補間に使用されるということです。 バインド変数は パラメータ化 のためだけに使用され、SQL文の構造を変更することは許されていません。 たとえば、バインド変数を使用して列名やテーブル名をパラメータ化しようとすると機能しません。
// 機能しない
db.Query("SELECT * FROM ?", "mytable")
// これも機能しない
db.Query("SELECT ?, ? FROM people", "name", "location")
#Query
Query
はdatabase/sql
を使用して行の結果を返すクエリを実行します。
下記のようにQuery
はsql.Rows
オブジェクトとエラーを返します。
// placeテーブルからすべてのデータを取得する
rows, err := db.Query("SELECT country, city, telcode FROM place")
// 各行を反復処理する
for rows.Next() {
var country string
// cityはNULLになる可能性があるため、NullString型を使用する
var city sql.NullString
var telcode int
err = rows.Scan(&country, &city, &telcode)
}
Rows
はクエリの結果を格納したリストというよりデータベースカーソルのように扱うべきです。
ドライバのバッファリング動作は異なる場合がありますが、Next()
を使って反復処理することは大きな結果セットのメモリ使用量を制限する良い方法です。
なぜなら、一度に1行ずつしかスキャン(scan)するからです。
Scan()
はreflectを使用して、sqlの列の戻り値の型をstring
、[]byte
などのGoの型にマッピングします。
全ての行の結果を反復処理しない場合は、接続をコネクションプールに戻すためにrows.Close()
を必ず呼び出してください。
Query
によって返されるエラーは、サーバ上での準備や実行中に発生した可能性のあるエラーです。
これには、コネクションプールから不良な接続を取得することも含まれますが、database/sql
は動作するコネクションを見つけたり作成したりするために10回まで再試行します。
一般的に、エラーは不正なSQL構文、型の不一致、または不正なフィールドやテーブル名によるものでしょう。
ほとんどの場合、Rows.Scan
はドライバがバッファをどのように再利用するかを知らないため、ドライバから取得したデータをコピーします。
sql.RawBytes
型の値をScan()
に渡すとドライバから実際に返されたデータの ゼロコピー バイトスライスを取得することがきます。
次のNext()
の呼び出し後、そのような値は有効でなくなります。
なぜなら、そのメモリはドライバによって上書きされている可能性があるからです。
Query
によって使用される接続はNext
による反復処理で全ての行が使い果たされるか、rows.Close()
が呼び出されるまでオープンなままです。
詳細については、コネクションプールのセクションを参照してください。
sqlx
のQueryx
はQuery
と全く同じように動作しますが、新たに機能追加されたスキャン(scan)を持つsqlx.Rows
を返します。
type Place struct {
Country string
City sql.NullString
TelephoneCode int `db:"telcode"`
}
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
var p Place
err = rows.StructScan(&p)
}
sqlx.Rows
のよく使う追加された機能はStructScan()
です。
これにより結果が自動的に渡された構造体のフィールドに割り当てられます。
sqlx
がそれらに書き込むことができるためには、フィールドがエクスポートされている(大文字で始まる)必要があることに注意してください。
これはGoの すべて のマーシャラー(marshaller)に当てはまることです。
db
構造体タグを使用して、どの列名が各構造体フィールドにマップするかを指定するか、db.MapperFunc()を使って新しいデフォルトマッピングを設定できます。
StructScan()
のデフォルトの動作はフィールド名にstrings.Lower
を使用して列名と一致させることです。
StructScan
、SliceScan
、MapScan
に関する詳しい情報は詳細なScanに関するセクションを見てください。
#QueryRow
QueryRow
はサーバから1行を取得します。
QueryRow
はコネクションプールから接続を取得し、Query
を使用してクエリを実行し、内部にRows
オブジェクトを持つRow
オブジェクトを返します。
row := db.QueryRow("SELECT * FROM place WHERE telcode=?", 852)
var telcode int
err = row.Scan(&telcode)
Query
とは異なり、QueryRow
はエラーなしでRow
型の結果を返します。
これにより、返されたものから直接Scan
を連鎖させることが安全になります。
クエリの実行中にエラーが発生した場合、そのエラーはScan
によって返されます。
該当する行がない場合、Scan
はsql.ErrNoRows
を返します。
スキャン自体が失敗した場合(例えば型の不一致のため)、そのエラーも返されます。
Row
結果内のRows
構造体はScan
時にクローズされます。
つまり、QueryRow
によって使用されるコネクションは、結果をスキャンするまで開いたままです。
また、これはsql.RawBytes
がここで使用できないことも意味します。
なぜなら、参照されたメモリはドライバに属しており、呼び出し元に制御が戻る時には既に無効になっている可能性があるからです。
sqlx
のQueryRowx
はsql.Row
の代わりにsqlx.Row
を返します。
上記および高度なスキャンで説明されているように、sqlx.Rows
と同じスキャン機能を実装しています。
var p Place
err := db.QueryRowx("SELECT city, telcode FROM place LIMIT 1").StructScan(&p)
#GetとSelect
Get
とSelect
は、データベース操作に関連した型の処理をまとめた関数です。
具体的にはクエリの実行と柔軟なスキャンをまとめています。
これらの処理を説明するために、ここでのスキャン可能
の定義を示します。
- 値が構造体でなければスキャン可能です。例えば
string
、int
など sql.Scanner
を実装していればスキャン可能です(例:time.Time
)- エクスポートされたフィールドを持たない構造体であればスキャン可能です
Get
とSelect
はスキャン可能な型に対して、rows.Scan
とrows.StructScan
をスキャン不可能な型に対して使います。
Get
とSelect
はQueryRow
とQuery
に似ています。
Get
は結果を1つ取得してスキャンすることに使います。
Select
は結果のスライスを取得することに使います。
p := Place{}
pp := []Place{}
// これは最初のレコードをpに割り当てます
err = db.Get(&p, "SELECT * FROM place LIMIT 1")
// これはtelcodeが50より大きいレコードをスライスppに割り当てます。
err = db.Select(&pp, "SELECT * FROM place WHERE telcode > ?", 50)
// intも利用可能です。
var id int
err = db.Get(&id, "SELECT count(*) FROM place")
// 名前を最大10個取得します。
var names []string
err = db.Select(&names, "SELECT name FROM place LIMIT 10")
Get
とSelect
は、クエリ実行中に作成されたRows
を閉じ、プロセスのどの段階で発生したエラーでも返します。
StructScan
を内部的に使用しているため、高度なスキャンに記載されている詳細もGet
とSelect
に適用されます。
Select
はコードを短くすることができますが、注意が必要です!
これはQueryx
と内部処理に異なり、結果セット全体を一度にメモリにロードします。
その結果セットがクエリによって何らかの合理的なサイズに制限されていない場合、
メモリを節約するためにQueryx
/StructScan
の反復処理を使った方が良いかもしれません。
#トランザクション
トランザクションを使用するには、DB.Begin()
でトランザクションハンドルを作成する必要があります。
以下のようなコードは 機能しません:
// コネクションプールが1より大きい場合、これは機能しません
db.MustExec("BEGIN;")
db.MustExec(...)
db.MustExec("COMMIT;")
Exec
や他のすべてのクエリ関数は、DBにコネクションを要求し、その都度プールに返します。
BEGIN文が実行されたのと同じコネクションを受け取る保証はありません。
したがって、トランザクションを使用するにはDB.Begin()
を使用する必要があります。
tx, err := db.Begin()
err = tx.Exec(...)
err = tx.Commit()
sqlx
はDBハンドルを生成するためにBeginx()
とMustBegin()
を用意しています。
これらはsql.Tx
の代わりにsqlx.Tx
を返します。
tx := db.MustBegin()
tx.MustExec(...)
err = tx.Commit()
sqlx.Tx
には、sqlx.DB
が持つすべての追加された関数があります。
トランザクションはコネクションの状態であるため、Tx
オブジェクトはプールから1つのコネクションをバインドし制御する必要があります。
Txはそのライフサイクル全体で1つのコネクションを維持し、Commit()
またはRollback()
が呼び出されたときにのみそれを解放します。
ガベージコレクションまでコネクションが保持されるのを防ぐため、これらのうち少なくとも1つを呼び出すように注意してください。
トランザクションでは1つのコネクションのみを使用するため、一度に1つのステートメントのみを実行できます。
カーソルタイプのRow
とRows
は、別のクエリを実行する前にそれぞれScanned
またはClosed
される必要があります。
サーバーが結果を送信している最中にデータを送信しようとすると、接続が破損する可能性があります。
最後に、Txオブジェクトはデータベースサーバで実際の動作を意味するものではありません。 BEGIN文を実行して1つのコネクションをバインドするだけです。 トランザクションの実際の動作(ロックや分離などを含む)は完全に仕様が決まっていません。 それらはデータベース依存です。
#プリペアドステートメント
ほとんどのデータベースではクエリが実行される毎に自動的にそのクエリのプリペアドステートメントを内部で作成しています。
しかし、下記のようにsqlx.DB.Prepare()
を使うと、他の場所で再利用するために明示的にステートメントを準備(prepare)することができます。
stmt, err := db.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = stmt.QueryRow(65)
tx, err := db.Begin()
txStmt, err := tx.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = txStmt.QueryRow(852)
Prepare
はデータベースサーバでステートメントの準備を行うために接続が必要です。
database/sql
はこれを抽象化し、新しいコネクションでステートメントを自動的に作成することで、1つのStmt
オブジェクトを多くのコネクションで同時に実行できるようにします。
Preparex()
はsqlx.DB
およびsqlx.Tx
が持つ全ての追加された機能を持つsqlx.Stmt
を返します。
stmt, err := db.Preparex(`SELECT * FROM place WHERE telcode=?`)
var p Place
err = stmt.Get(&p, 852)
sql.Tx
オブジェクトには既存のステートメントからトランザクション固有のステートメントを返すStmt()
メソッドがあります。
sqlx.Tx
にはStmtx
バージョンがあり、既存のsql.Stmt
または sqlx.Stmt
から新しいトランザクション固有のsqlx.Stmt
を作成します。
#クエリヘルパー
database/sql
パッケージは実際のクエリテキストに対して何も行いません。
これにより、コード内で特定のデータベースエンジン専用の機能を使用することが非常に簡単になります。
データベースプロンプトで行うようにクエリをそのまま書くことができます。
これは非常に柔軟ですが特定の種類のクエリを書くことが難しくなります。
#IN句
database/sql
はクエリを検査せず、引数をそのままドライバに渡すため、IN句を含むクエリの扱いが難しくなります:
SELECT * FROM users WHERE level IN (?);
このクエリがデータベースサーバでステートメントとして準備されると、バインド変数?
は 1つ の引数にのみ対応します。
しかし、通常はいくつかのスライスの長さに応じて変数数の引数であることが望まれます。例えば
var levels = []int{4, 6, 7}
rows, err := db.Query("SELECT * FROM users WHERE level IN (?);", levels)
このパターンは、最初にsqlx.In
でクエリを処理することにより可能になります:
var levels = []int{4, 6, 7}
query, args, err := sqlx.In("SELECT * FROM users WHERE level IN (?);", levels)
// sqlx.Inは`?`バインド変数でクエリを返します。これを使用しているデータベースエンジン用にに再バインドできます。
query = db.Rebind(query)
rows, err := db.Query(query, args...)
sqlx.In
が行うことはそれに渡されたクエリ内の任意のバインド変数を引数内のスライスに対応するスライスの長さに拡張します。
そして、そのスライス要素を新しい引数リストに追加することです。
これは?
バインド変数のみで行われます。
データベースエンジンに適合したクエリに変換ためにdb.Rebind
を使います。
#名前付きクエリ
名前付きクエリは多くの他のデータベースパッケージで一般的です。
これらはバインド変数構文を使用し、構造体のフィールド名やマップのキー名に基づいてクエリ変数をバインドすることを可能にします。
これにより、バインド変数の位置に基づいてすべてのクエリ変数を参照する必要がなくなります。
構造体フィールドの命名規則はStructScan
に従い、NameMapper
およびdb
構造体タグを使用します。
名前付きクエリに関連する追加のクエリ関数は次のとおりです:
NamedQuery(...) (*sqlx.Rows, error)
-Queryx
と似ていますが、名前付きバインド変数を使用します。NamedExec(...) (sql.Result, error)
-Exec
と似ていますが、名前付きバインド変数を使用します。
そして、追加のデータベース操作に関連した型が1つあります:
NamedStmt
- 名前付きバインド変数で準備できるsqlx.Stmt
です。
// 構造体を使った名前付きクエリ
p := Place{Country: "South Africa"}
rows, err := db.NamedQuery(`SELECT * FROM place WHERE country=:country`, p)
// マップを使った名前付きクエリ
m := map[string]interface{}{"city": "Johannesburg"}
result, err := db.NamedExec(`SELECT * FROM place WHERE city=:city`, m)
名前付きクエリの実行と準備は構造体とマップの両方で行われます。 下記のように名前付きステートメントを準備して、クエリ関数を実行します。
type Place struct {
TelephoneCode int `db:"telcode"`
}
pp := []Place{}
// telcode > 50の全てを選択
nstmt, err := db.PrepareNamed(`SELECT * FROM place WHERE telcode > :telcode`)
err = nstmt.Select(&pp, p)
名前付きクエリの処理は:param
構文のクエリ解析と基になるデータベースがサポートするバインド変数への置き換えます。
そして、実行に変数をマッピングします。
したがって、sqlx
がサポートする任意のデータベースで使用可能です。
下記のように、sqlx.Named
を使用することもできます。
これはsqlx.In
と組み合わせて使用することができます。
arg := map[string]interface{}{
"published": true,
"authors": []{8, 19, 32, 44},
}
query, args, err := sqlx.Named("SELECT * FROM articles WHERE published=:published AND author_id IN (:authors)", arg)
query, args, err := sqlx.In(query, args...)
query = db.Rebind(query)
db.Query(query, args...)
#高度なスキャン
StructScan
は見かけよりも洗練されています。
この関数は埋め込み構造体をサポートし、Goが埋め込み属性やメソッドアクセスに使用するのと同じ優先順位ルールを用いてフィールドに割り当てます。
これの一般的な使用例は、多くのテーブルでテーブルモデルの共通部分を共有することです。
例えば
type AutoIncr struct {
ID uint64
Created time.Time
}
type Place struct {
Address string
AutoIncr
}
type Person struct {
Name string
AutoIncr
}
上記の構造体では、Person
とPlace
は共にStructScan
からid
とcreated
列を受け取ることができます。
なぜなら、それらはAutoIncr
構造体を埋め込んでおり、それがこれらを定義しているからです。この機能により、結合のための臨時テーブルを迅速に作成することができます。
また、この機能は再帰的にも動作します。
以下の例では、Person
のName
とそのAutoIncr
のID
とCreated
フィールドが、Goのドット演算子(例: employee.id
)とStructScan
(例: id
カラムの値をemployee.id
に格納する)の両方を通してアクセス可能です。
type Employee struct {
BossID uint64
EmployeeID uint64
Person
}
非埋め込み構造体に対しても、以前はsqlx
がこの機能をサポートしていました。しかし、これは混乱を招く原因となりました。
というのも、ユーザーがこの機能を使って関係性を定義し、下記のように同じ構造体を二度埋め込むことと同じ効果が生じるような構造体を定義しました。
type Child struct {
Father Person
Mother Person
}
これにはいくつかの問題があります。
Goでは、子孫のフィールドをシャドウ(覆い隠す)することは動作します。
つまり、埋め込みの例で示したEmployee
がName
を定義していれば、それはPerson
のName
よりも優先されます。
しかし、曖昧なセレクタ(ambiguous selectors)は動作しません。
ランタイムエラーを引き起こします。
Person
とPlace
に対する効率的なJOINを反映した型を作成したい場合、埋め込んだAutoIncr
経由で定義されているid
列をどこに配置すればよいでしょうか?エラーになるでしょうか?
sqlx
がフィールド名からフィールドアドレスへのマッピングを構築する方法に従って構造体へのスキャンではカラム名前が構造体ツリーの走査中に二度出会ったかどうかを考慮しません。
したがって、Goとは異なり、StructScan
はその名前を持つ「最初」に出会ったフィールドを選択します。
Goの構造体フィールドは上から下へと順序付けられており、sqlx
が優先順位ルールを維持するために幅優先探索を行うため、これは最も浅く、最も上部の定義で発生します。
例えば、次の型では
type PersonPlace struct {
Person
Place
}
StructScan
はid
列の結果をPerson.AutoIncr.ID
に設定し、Person.ID
としてもアクセスできます。
混乱を避けるためには、SQLでAS
を使用して列エイリアスを作成することをお勧めします。
#スキャン先の安全性について
デフォルトでは、StructScan
はカラムが対象のフィールドにマッピングされていない場合にエラーを返します。
これはGoでの未使用変数の扱いに似ていますが、encoding/json
のような標準ライブラリのマーシャラーの動作とは一致しません。
SQLは一般的にJSONのパースよりも予め定義された方法で実行されます。
これらのエラーは通常コーディングエラーだと判断したので、デフォルトでエラーを返すことに決められました。
未使用の変数と同様に、無視されるカラムはネットワークやデータベースのリソースの無駄です。 そして、不適合なマッピングや構造体タグのタイポなどを早期に検出するのは、マッパーが何かが見つからなかったことを教えてくれない限り難しいでしょう。
それにもかかわらず、マッピング対象がないカラムを無視したい場合もあります。
そのために、安全性をオフにしたそのデータベース操作に関連した型のインスタンスの新しいコピーを返すUnsafe
メソッドがあります。
var p Person
// ここでのerrはnilではありません。`place`のカラムに対応するフィールドがありません
err = db.Get(&p, "SELECT * FROM person, place LIMIT 1;")
// `place`のカラムに対応するフィールドがなくても、これはエラーを返しません
udb := db.Unsafe()
err = udb.Get(&p, "SELECT * FROM person, place LIMIT 1;")
#名前マッピングの制御
StructScans
でデータの保存先として使用される構造体のフィールドは、sqlx
によってアクセス可能であるために大文字でなければなりません。
このため、sqlx
は NameMapper を使用してフィールド名にstrings.ToLower
を適用し、行結果のカラムにマッピングします。
これはスキーマによっては常に望ましいわけではないので、sqlx
ではマッピングをいくつかの方法でカスタマイズすることができます。
これらの方法の中で最もシンプルなのは、sqlx.DB.MapperFunc
を使用してDBハンドルに設定することです。
func(string) string
型の引数を受け取ります。
特定のマッパーが必要なライブラリを使用しており、受け取ったsqlx.DB
を汚染したくない場合は、特定のデフォルトマッピングを保証するためにライブラリで使用するためのコピーを作成することができます:
// もしDBスキーマがALLCAPSカラムを使用している場合、通常のフィールドを使うことができます
db.MapperFunc(strings.ToUpper)
// あるライブラリが小文字のカラムを使用していると仮定すると、コピーを作成することができます
copy := sqlx.NewDb(db.DB, db.DriverName())
copy.MapperFunc(strings.ToLower)
各sqlx.DB
はsqlx/reflectx
パッケージのMapperを使用してこのマッピングを行います。
各sqlx.DB
はアクティブなマッパーをsqlx.DB.Mapper
として公開しています。
DBに直接設定することで、さらにマッピングをカスタマイズすることができます。
import "github.com/jmoiron/sqlx/reflectx"
// "db"の代わりに構造体フィールドタグ"json"を使用する新しいマッパーを作成する
db.Mapper = reflectx.NewMapperFunc("json", strings.ToLower)
#スライスやマップにスキャンする
Scan
やStructScan
だけでなく、sqlx
のRow
やRows
は結果をスライスやマップに格納するメソッドを持っています。
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
// colsはすべてのカラム結果の[]interface{}です
cols, err := rows.SliceScan()
}
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
results := make(map[string]interface{})
err = rows.MapScan(results)
}
SliceScan
はすべてのカラムの[]interface{}
を返し、第三者のためにクエリを実行しているsituationsのような状況で、どのカラムが返されるかわからない場合に便利です。
MapScan
も同様の方法で動作しますが、キーをカラム名、値をinterface{}
のマップにマッピングします。
ここでの重要な注意点は、rows.Columns()
によって返される結果にはテーブル名を含むカラム名が含まれていないので、SELECT a.id, b.id FROM a NATURAL JOIN b
は[]string{"id", "id"}
のカラム名の配列になり、マップ内の1つの結果が上書きされることです。
#カスタムタイプ
上記の例ではすべてビルトインのタイプを使用してスキャンやクエリを行っていますが、
database/sql
は任意のカスタムタイプを使用するためのインターフェースを提供しています。
sql.Scanner
はScan()でカスタムタイプを使用するために使用されますdriver.Valuer
はQuery/QueryRow/Execでカスタムタイプを使用するために使用されます
これらは標準的なインターフェースであり、database/sql
の上にサービスを提供している可能性のある任意のライブラリに移植性を保証するために使用されます。
これらの使用方法の詳細については、このブログ記事を読むか、
いくつかの標準的で有用なタイプを実装しているsqlx/typesパッケージを確認してください。
#コネクションプール
ステートメントの準備とクエリの実行には接続が必要です。 DBオブジェクトは接続のプールを管理して、並行クエリで安全に使用できるようにします。 Go 1.2時点でコネクションプールのサイズを制御する方法は2つあります。
DB.SetMaxIdleConns(n int)
DB.SetMaxOpenConns(n int)
デフォルトではプールは無制限に拡大します。
そして、プール内に利用可能な接続がない場合はいつでも接続が作成されます。
DB.SetMaxOpenConns
を使用してプールの最大サイズを設定できます。
使用されていない接続はアイドル状態とされ、必要がなければ閉じられます。
接続を頻繁に作成・閉鎖することを避けるために、DB.SetMaxIdleConns
を使用してクエリ負荷に合理的なサイズで最大アイドルサイズを設定します。
接続を誤って保持することにより問題が発生しやすいです。 これを防ぐために以下のことに注意してください。
- すべてのRowオブジェクトで
Scan()
を確実に行います - すべてのRowsオブジェクトを
Close()
するか、Next()
を使用して完全に繰り返します - すべてのトランザクションが
Commit()
かRollback()
を使用して接続を返します
これらのいずれかを怠ると、それらが使用する接続はガベージコレクションまで保持される可能性があります。
DBは使用中のものを補うために一度にはるかに多くの接続を作成することになります。
Rows.Close()
は何度でも安全に呼び出すことができるので、必要がない場合でもそれを呼び出しても大丈夫です。
#License
MIT License
Copyright (c) 2013, Jason Moiron
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.