Golang SQL操作初体験


概要
Golangはdatabase/sqlパッケージを提供してSQLのデータベースへのアクセスに使用して、このパッケージの中で最も重要なのは自然にsqlです.DBだsqlについて.DBは、既存のデータベースの抽象アクセスインタフェースであることを強調する必要があります.sql.DBは2つの重要な機能を提供しています.
  • sql.DBは、データベース駆動により、最下位のデータベース接続の開閉操作を管理する.
  • sql.DBは、データベース接続プール
  • を管理しています.
    一つ注意しなければならないのは、sqlのせいだ.DBは接続プールとしてデータベース接続を管理するものであり、データベース操作を行うたびに接続プールから接続を取り出す必要があり、操作タスクが完了すると、この接続を接続プールに戻す必要があるため、接続プールに正しく戻すとdbになる.SQLはデータベース接続を開きすぎて、データベース接続資源を消耗させる.
    MySQLデータベースの基本操作
    データベースドライバのインポート
    データベースの開発経験のある友人は知っています.具体的なデータベースと接続するには、データベースの駆動を借りなければなりません.これはGolangにおいても例外ではない.たとえば、MySQLデータベースを例に挙げます.
    import (
        "database/sql"
        _ "github.com/go-sql-driver/mysql"
    )

    通常、駆動によって提供する方法を直接使用するのではなく、sqlを使用するべきであることに注意すべきである.DBであるためmysqlドライバを導入する際に匿名で導入する方式(パケットパスの前に_を追加)を用いた.データベースドライバをインポートすると、Golangのdatabase/sqlコンテキストに独自に初期化して登録するので、database/sqlパッケージで提供する方法でデータベースにアクセスできます.
    データベースの接続
    MySQLドライバをインポートすると、データベース接続を開きます.
    func main() {
        db, err := sql.Open("mysql",
            "user:password@tcp(127.0.0.1:3306)/test")
        if err != nil {
            log.Fatal(err)
        }
        defer db.Close()
    }

    sqlを通ります.Open関数は、データベース抽象操作インタフェースを作成することができ、正常に開くとsqlを返す.DBポインタsql.Open関数の署名は次のとおりです.
    func Open(driverName, dataSourceName string) (*DB, error)

    2つのパラメータを受信します.
  • driverName、使用するドライバ名.この名前は、データベースドライバがdatabase/sqlに登録する際に使用する名前です.
  • dataSourceName、2番目のデータベース接続のリンク.このリンクには、データベースのユーザ名、パスワード、データベースホスト、接続するデータベース名などの情報が含まれている.
  • 最初のデータベースクエリー操作を行うと、ネットワーク接続が確立されます.データベース接続が使用可能かどうかをすぐに確認するには、sqlを使用します.DBのPingメソッドは、次のようになります.
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    sql.DBのベストプラクティス:sql.DBオブジェクトは長期生存のオブジェクトとして使用するため、Open()とClose()を頻繁に呼び出すことは避けるべきである.すなわち、一般的に、データベースを操作するときにsqlを作成する.DBはそれを保存する、このデータベースを操作するたびにこのsqlを渡す.DBオブジェクトでよい、最後にこのデータベースにアクセスする必要がある場合には、対応するsqlを閉じる.DBオブジェクト
    データベースのクエリー
    データベース・クエリーの一般的な手順は次のとおりです.
  • dbが呼び出される.QueryはSQL文を実行し、クエリの結果としてRowsを返す
  • rowsを通過する.Next()反復クエリデータ.
  • rowsを通過する.Scan()は、各ローの値
  • を読み出す.
  • dbが呼び出される.Close()クエリー
  • を閉じる
    たとえば、次のデータベース・テーブルがあります.
    CREATE TABLE `user` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) DEFAULT '',
      `age` int(11) DEFAULT '0',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4

    レコードを挿入します.
    func insertData(db *sql.DB) {
        rows, err := db.Query(`INSERT INTO user (id, name, age) VALUES (1, "xys", 20)`)
        defer rows.Close()
        if err != nil {
            log.Fatalf("insert data error: %v
    ", err) } var result int rows.Scan(&result) log.Printf("insert result %v
    ", result) }

    dbを呼び出す.Query、INSERT文を実行してデータを挿入しました.実行が完了すると、まず文が成功したかどうかを確認し、エラーがない場合はrowsを通過する必要があります.Scanは実行結果を取得する.INSERTは挿入するデータの行数を返すので、私たちが印刷する文は「insert result 0」です.
    次に、データベースから挿入したデータを取り出します.
    func selectData(db *sql.DB) {
        var id int
        var name string
        var age int
        rows, err := db.Query(`SELECT * From user where id = 1`)
        if err != nil {
            log.Fatalf("insert data error: %v
    ", err) return } for rows.Next() { rows.Scan(&id, &age, &name) if err != nil { log.Fatal(err) } log.Printf("get data, id: %d, name: %s, age: %d", id, name, age) } err = rows.Err() if err != nil { log.Fatal(err) } }

    上のコードの流れは基本的に大きな違いはありませんが、rowsに注意する必要があります.Scanパラメータの順序は重要で、クエリの結果のcolumnに対応する必要がある.例えば「SELECT*From user where id=1」クエリの行のcolumn順序は「id,name,age」であるためrows.Scanもこの順序でrowsする必要がある.Scan(&id,&name,&age)は、データ読み出しのずれをもたらす. , golang (lazy init), sql.Open , , sql.DB . :
  • データベース操作ごとに、エラーがあるかどうかを確認する必要があります.
  • に戻ります.
  • 毎回db.Query操作後、rowsを呼び出す必要があります.Close(). なぜならdb.Query()は、rowsを呼び出していない場合、データベース接続プールから接続を取得します.Close()の場合、この接続は常に占有されます.通常はdefer rowsを使用しますClose()は、データベース接続が接続プールに正しく戻すことを保証する.
  • rowsを複数回呼び出す.Close()は副作用がないので、rowsを表示的に呼び出してもよい.Close()はdefer rowsを使うべきだClose()を使用してクエリーを閉じます.

  • 完全な例は次のとおりです.
    func insertData(db *sql.DB) {
        rows, err := db.Query(`INSERT INTO user (id, name, age) VALUES (1, "xys", 20)`)
        defer rows.Close()
    
        if err != nil {
            log.Fatalf("insert data error: %v
    ", err) } var result int rows.Scan(&result) log.Printf("insert result %v
    ", result) } func selectData(db *sql.DB) { var id int var name string var age int rows, err := db.Query(`SELECT id, name, age From user where id = 1`) if err != nil { log.Fatalf("insert data error: %v
    ", err) return } for rows.Next() { err = rows.Scan(&id, &name, &age) if err != nil { log.Fatal(err) } log.Printf("get data, id: %d, name: %s, age: %d", id, name, age) } err = rows.Err() if err != nil { log.Fatal(err) } } func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test") defer db.Close() if err != nil { fmt.Printf("connect to db 127.0.0.1:3306 error: %v
    ", err) return } insertData(db) selectData(db) }

    プリコンパイル文(Prepared Statement)
    プレコンパイル文(PreparedStatement)は多くのメリットを提供するので、開発ではできるだけ使用します.以下に、プリコンパイル文を使用して提供される機能を示します.
  • PreparedStatementは、カスタムパラメータのクエリ
  • を実装することができる
  • PreparedStatementは、通常、手動文字列SQL文よりも効率的である.
  • PreparedStatement SQL注入攻撃
  • を防止
    次に、前節の例をPrepared Statementで書き換えます.
    func deleteData(db *sql.DB) {
        stmt, _ := db.Prepare(`DELETE FROM user WHERE id = ?`)
    
        rows, err := stmt.Query(1)
        defer stmt.Close()
    
        rows.Close()
        if err != nil {
            log.Fatalf("delete data error: %v
    ", err) } rows, err = stmt.Query(2) rows.Close() if err != nil { log.Fatalf("delete data error: %v
    ", err) } } func insertData(db *sql.DB) { stmt, _ := db.Prepare(`INSERT INTO user (id, name, age) VALUES (?, ?, ?)`) rows, err := stmt.Query(1, "xys", 20) defer stmt.Close() rows.Close() if err != nil { log.Fatalf("insert data error: %v
    ", err) } rows, err = stmt.Query(2, "test", 19) var result int rows.Scan(&result) log.Printf("insert result %v
    ", result) rows.Close() } func selectData(db *sql.DB) { var id int var name string var age int stmt, _ := db.Prepare(`SELECT * From user where age > ?`) rows, err := stmt.Query(10) defer stmt.Close() defer rows.Close() if err != nil { log.Fatalf("select data error: %v
    ", err) return } for rows.Next() { err = rows.Scan(&id, &name, &age) if err != nil { log.Fatal(err) } log.Printf("get data, id: %d, name: %s, age: %d", id, name, age) } err = rows.Err() if err != nil { log.Fatal(err) } } func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test") defer db.Close() if err != nil { fmt.Printf("connect to db 127.0.0.1:3306 error: %v
    ", err) return } deleteData(db) insertData(db) selectData(db) }