はじめに
こんにちは!青柳と申します。バックエンドで最近Goを使うことが多くなってきていて、その中でデータを操作するときに便利だったsqlxライブラリをご紹介します。
いろいろ機能はありますが、今回は主にStructにデータを積めるScanの使用方法についてお話しします!
sqlxとは
Goでよく使用するライブラリとしてはdatabase/sqlという標準ライブラリが挙げられます。基本的にこちらでも問題ないのですが、データを取得したあと、構造体にそのデータを格納してクライアントに返すとき、不便なことがあります。
それらの問題を解決してくれるのがsqlxというライブラリです。こちらはdatabase/sqlの拡張ライブラリとなっています。
https://pkg.go.dev/database/sql
https://pkg.go.dev/github.com/jmoiron/sqlx
sqlで実装した場合
package main
import (
    "fmt"
    "log"
    _ "github.com/go-sql-driver/mysql"
    "database/sql"
)
// Structを用意
type User struct {
    ID        int      `json: "id"`
    Name string `json: "name"`
    Email  int       `json: "age"`
    Tel      string `json: "tel"`
}
func main() {
    //DBに接続する
    db, err := sql.Open("mysql", "root/sample")
    if err != nil {
        defer db.Close()
        log.Fatal(err)
    }
    // SQL実行
    rows, err := db.Query("SELECT id, name, email, tel FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
    results := make([]User, 0)
    for rows.Next() {
        var user User
        err := rows.Scan(
            &user.ID,
            &user.Name,
            &user.Email,
            &user.Tel
        )
        if err != nil {
            log.Fatal(err)
        }
        results = append(results, user)
    }
    fmt.Println(results)
}特に注目していただきたいのは
  err := rows.Scan(
            &user.ID,
            &user.Name,
            &user.Email,
            &user.Tel
        )の部分です。sqlのScanは下記のような特性を持ちます。
- データと同じ個数の引数が必要
 - データの当てはめは順番に依存する。(例えば、上記SQLのSELECT句がid, name, email, telとなっているので、Scanの引数の順番も必ず、上記の通りじゃないといけない。&user.Name、&user.ID、、、のように順番を入れ替えるとエラーまたは適切に値を取得できない)
 
そのため、
- データが増えたとき、その分Scanの引数を書く必要があるため、冗長になりやすい。
 - 動的にデータの個数が変わる場合、それに合わせたScanを用意する必要がある。(データ個数が3つの場合は引数が3個のScan、10個の場合は引数が10個のScanを用意する)
 - 順番が不正でもStructの型が同じだとエラーにならないのでバグになりやすい。
 
という弱点を持ちます。これを解決してくれるのがsqlxライブラリです。
sqlxで実装した場合
上記のsqlを使用して書いたソースコードをsqlxで書き直してみましょう。
package main
import (
    "fmt"
    "log"
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
)
// Structを用意
type User struct {
    ID        int      `db: "id" json: "id"`
    Name string `db: "name" json: "name"`
    Email  int       `db: "age" json: "age"`
    Tel      string `db: "tel" json: "tel"`
}
func main() {
    //DBに接続する
    db, err := sqlx.Open("mysql", "root/sample")
    if err != nil {
        defer db.Close()
        log.Fatal(err)
    }
    // SQL実行
    rows, err := db.Queryx("SELECT id, name, email, tel FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
    results := make([]User, 0)
    for rows.Next() {
        var user User
        err := rows.StructScan(&user)
        if err != nil {
            log.Fatal(err)
        }
        results = append(results, user)
    }
    fmt.Println(results)
}変更点を見ていきましょう。
“github.com/jmoiron/sqlx”はライブラリを読み込むために必要なので記載してください。
type User struct {
    ID        int      `db: "id" json: "id"`
    Name string `db: "name" json: "name"`
    Email  int       `db: "age" json: "age"`
    Tel      string `db: "tel" json: "tel"`
}Struct部分はjson: “id”からdb: “id” json: “id”のように変更します。db:とすることでsqlxのStructScanがStructを読み込むことができるようになります。
また、json: “id”を残しているのは、レスポンスのオブジェクトのkeyをIDではなくidで返したいからです。ID int `db: “id”`としても機能するのですが、この場合、レスポンスが
{
  ID: 1
}のようになります。もし、
{
 id: 1
}のようにデータベースのカラムと同じ形で返したい場合はdb: “id” json: “id”のようにしてください。これはデータを受け取るクライアントの要件によると思いますので都度検討していただければと思います。
rows, err := db.Queryx("SELECT id, name, email, tel FROM users")sqlを使用していたときはQueryでしたが、sqlxではQueryxというメソッドを使用します。他にも多数独自のメソッドがありますので詳しくは上記リンクのドキュメントをご覧ください。
err := rows.StructScan(&user)最後にStructへデータを当てはめるScanですが上記のようにStructScanを使用します。その際、sqlではデータの数だけ引数に&struct.プロパティを用意する必要がありましたが、sqlxのStructScanではStructのポインタを渡すだけで完結します。これで自動的にデータを投入してくれるので、動的にデータの個数が変わる場合も対応できますし、順番を気にする必要性もなくて非常に便利です。
まとめ
いかがでしたでしょうか?標準ライブラリをそのまま使用しても問題はありませんが拡張されたsqlxを使用することで弱点を補うことができます。また、拡張ライブラリなので(あまりお勧めしませんが)sqlとsqlxを同時に使用することも場合によっては可能です。
上記以外にもいろいろな拡張機能があるので是非お試しください。
読んでいただきありがとうございました!


