前提条件

  • MySQL リレーショナルデータベース管理システム (DBMS) のインストール。
  • Go のインストール。 インストール手順については、Installing Go を参照してください。
  • コードを編集するためのツール。 お使いのテキストエディタで問題ありません。
  • コマンドターミナル。 Go は、Linux や Mac の任意のターミナル、または Windows の PowerShell や cmd でうまく動作します。

コード用のフォルダーを作成する

まず、作成するコード用のフォルダーを作成します。

  • 1. コマンドプロンプトを開き、ホームディレクトリに移動します。
    Linux または Mac の場合:
    1. $ cd
    Windows の場合:
    1. C:\> cd %HOMEPATH%
    チュートリアルの残りの部分では、プロンプトとして $ を表示します。使用するコマンドは Windows でも動作します。
  • 2. コマンドプロンプトから、コード用のディレクトリを data-access という名前で作成します。
    1. $ mkdir data-access
    2. $ cd data-access
  • 3. このチュートリアル中に追加する依存関係を管理するためのモジュールを作成します。
    go mod init コマンドを実行し、新しいコードのモジュールパスを指定します。
    1. $ go mod init example/data-access
    2. go: creating new go.mod: module example/data-access
    このコマンドは、追加した依存関係が追跡される go.mod ファイルを作成します。詳細については、Managing dependencies を必ず参照してください。
    注意: 実際の開発では、より具体的なニーズに応じたモジュールパスを指定します。詳細については、Managing dependencies を参照してください。

次に、データベースを作成します。

データベースを設定する

このステップでは、作業するデータベースを作成します。データベースとテーブルを作成し、データを追加するために、DBMS 自体の CLI を使用します。

ビンテージジャズレコーディングに関するデータを持つデータベースを作成します。

ここでのコードは MySQL CLI を使用していますが、ほとんどの DBMS には同様の機能を持つ独自の CLI があります。

  • 1. 新しいコマンドプロンプトを開きます。
  • 2. コマンドラインで、DBMS にログインします。以下は MySQL の例です。

    1. $ mysql -u root -p
    2. Enter password:
    3. mysql>
  • 3. mysql コマンドプロンプトで、データベースを作成します。
    1. mysql> create database recordings;
  • 4. 作成したばかりのデータベースに切り替えて、テーブルを追加できるようにします。
    1. mysql> use recordings;
    2. Database changed
  • 5. テキストエディタで、data-access フォルダー内に create-tables.sql という名前のファイルを作成し、テーブルを追加するための SQL スクリプトを保持します。
  • 6. ファイルに次の SQL コードを貼り付けて、ファイルを保存します。

    1. DROP TABLE IF EXISTS album;
    2. CREATE TABLE album (
    3. id INT AUTO_INCREMENT NOT NULL,
    4. title VARCHAR(128) NOT NULL,
    5. artist VARCHAR(255) NOT NULL,
    6. price DECIMAL(5,2) NOT NULL,
    7. PRIMARY KEY (`id`)
    8. );
    9. INSERT INTO album
    10. (title, artist, price)
    11. VALUES
    12. ('Blue Train', 'John Coltrane', 56.99),
    13. ('Giant Steps', 'John Coltrane', 63.99),
    14. ('Jeru', 'Gerry Mulligan', 17.99),
    15. ('Sarah Vaughan', 'Sarah Vaughan', 34.98);

    この SQL コードでは、次のことを行います:

    • album という名前のテーブルを削除 (ドロップ) します。このコマンドを最初に実行することで、テーブルを再作成したい場合にスクリプトを再実行しやすくなります。
    • album テーブルを作成し、4 つの列を持ちます: titleartistprice。各行の id 値は DBMS によって自動的に作成されます。
    • 値を持つ 4 行を追加します。
  • 7. mysql コマンドプロンプトから、作成したスクリプトを実行します。
    次の形式で source コマンドを使用します:
    1. mysql> source /path/to/create-tables.sql
  • 8. DBMS コマンドプロンプトで、SELECT ステートメントを使用して、データを持つテーブルが正常に作成されたことを確認します。
    1. mysql> select * from album;
    2. +----+---------------+----------------+-------+
    3. | id | title | artist | price |
    4. +----+---------------+----------------+-------+
    5. | 1 | Blue Train | John Coltrane | 56.99 |
    6. | 2 | Giant Steps | John Coltrane | 63.99 |
    7. | 3 | Jeru | Gerry Mulligan | 17.99 |
    8. | 4 | Sarah Vaughan | Sarah Vaughan | 34.98 |
    9. +----+---------------+----------------+-------+
    10. 4 rows in set (0.00 sec)

次に、クエリを実行できるように接続するための Go コードを書きます。

データベースドライバーを見つけてインポートする

データベースにデータがあるので、Go コードを開始します。

リクエストを database/sql パッケージの関数を通じてデータベースが理解できるリクエストに変換するデータベースドライバーを見つけてインポートします。

  • 1. ブラウザで SQLDrivers ウィキページにアクセスして、使用できるドライバーを特定します。
    ページのリストを使用して、使用するドライバーを特定します。このチュートリアルで MySQL にアクセスするために、Go-MySQL-Driver を使用します。
  • 2. ドライバーのパッケージ名をメモします - ここでは github.com/go-sql-driver/mysql です。
  • 3. テキストエディタを使用して、Go コードを書くためのファイルを作成し、以前に作成した data-access ディレクトリに main.go として保存します。
  • 4. main.go に、ドライバーパッケージをインポートするための次のコードを貼り付けます。

    1. package main
    2. import "github.com/go-sql-driver/mysql"

    このコードでは、次のことを行います:

    • コードを main パッケージに追加して、独立して実行できるようにします。
    • MySQL ドライバー github.com/go-sql-driver/mysql をインポートします。

ドライバーがインポートされたので、データベースにアクセスするためのコードを書き始めます。

データベースハンドルを取得して接続する

データベースハンドルを使用してデータベースアクセスを提供する Go コードを書きます。

特定のデータベースへのアクセスを表す sql.DB 構造体へのポインタを使用します。

コードを書く

  • 1. main.go の import コードの下に、データベースハンドルを作成するための次の Go コードを貼り付けます。

    1. var db *sql.DB
    2. func main() {
    3. // Capture connection properties.
    4. cfg := mysql.Config{
    5. User: os.Getenv("DBUSER"),
    6. Passwd: os.Getenv("DBPASS"),
    7. Net: "tcp",
    8. Addr: "127.0.0.1:3306",
    9. DBName: "recordings",
    10. }
    11. // Get a database handle.
    12. var err error
    13. db, err = sql.Open("mysql", cfg.FormatDSN())
    14. if err != nil {
    15. log.Fatal(err)
    16. }
    17. pingErr := db.Ping()
    18. if pingErr != nil {
    19. log.Fatal(pingErr)
    20. }
    21. fmt.Println("Connected!")
    22. }

    このコードでは、次のことを行います:

    • db 型の *sql.DB 変数を宣言します。これがデータベースハンドルです。
      db をグローバル変数にすることで、この例が簡素化されます。実際のプロダクションでは、必要な関数に変数を渡すか、構造体にラップすることでグローバル変数を避けるべきです。
    • MySQL ドライバーの Config と型の FormatDSN を使用して、接続プロパティを収集し、接続文字列の DSN にフォーマットします。
      Config 構造体は、接続文字列よりも読みやすいコードを作成します。
    • sql.Open を呼び出して、db 変数を初期化し、FormatDSN の戻り値を渡します。
    • sql.Open からエラーをチェックします。たとえば、データベース接続の詳細が正しくない場合、失敗する可能性があります。
      コードを簡素化するために、log.Fatal を呼び出して実行を終了し、エラーをコンソールに出力します。プロダクションコードでは、エラーをより優雅に処理することをお勧めします。
    • DB.Ping を呼び出して、データベースへの接続が機能することを確認します。実行時に、sql.Open はドライバーによって即座に接続されない場合があります。ここでは、Ping を使用して、database/sql パッケージが必要なときに接続できることを確認します。
    • Ping からエラーをチェックし、接続が失敗した場合に備えます。
    • Ping が正常に接続された場合は、メッセージを出力します。
  • 2. main.go ファイルの先頭近く、パッケージ宣言のすぐ下に、書いたコードをサポートするために必要なパッケージをインポートします。
    ファイルの先頭は次のようになります:

    1. package main
    2. import (
    3. "database/sql"
    4. "fmt"
    5. "log"
    6. "os"
    7. "github.com/go-sql-driver/mysql"
    8. )
  • 3. main.go を保存します。

コードを実行する

  • 1. MySQL ドライバーモジュールを依存関係として追跡し始めます。
    go get を使用して、github.com/go-sql-driver/mysql モジュールを自分のモジュールの依存関係として追加します。ドット引数を使用して「現在のディレクトリのコードの依存関係を取得する」ことを意味します。
    1. $ go get .
    2. go get: added github.com/go-sql-driver/mysql v1.6.0
    Go は、前のステップで import 宣言に追加したため、この依存関係をダウンロードしました。依存関係の追跡についての詳細は、Adding a dependency を参照してください。
  • 2. コマンドプロンプトから、Go プログラムで使用する DBUSERDBPASS 環境変数を設定します。
    Linux または Mac の場合:
    1. $ export DBUSER=username
    2. $ export DBPASS=password
    Windows の場合:
    1. C:\Users\you\data-access> set DBUSER=username
    2. C:\Users\you\data-access> set DBPASS=password
  • 3. main.go を含むディレクトリのコマンドラインから、go run を入力してコードを実行します。ドット引数を使用して「現在のディレクトリのパッケージを実行する」ことを意味します。
    1. $ go run .
    2. Connected!

接続できます!次に、データをクエリします。

複数行をクエリする

このセクションでは、Go を使用して複数行を返すように設計された SQL クエリを実行します。

複数行を返す可能性のある SQL ステートメントには、Query メソッドを使用し、database/sql パッケージから返された行をループします。(後で、Query for a single row セクションで、単一行をクエリする方法を学びます。)

コードを書く

  • 1. main.go の func main のすぐ上に、Album 構造体の定義を貼り付けます。これを使用して、クエリから返された行データを保持します。
    1. type Album struct {
    2. ID int64
    3. Title string
    4. Artist string
    5. Price float32
    6. }
  • 2. func main の下に、データベースをクエリするための次の albumsByArtist 関数を貼り付けます。

    1. // albumsByArtist queries for albums that have the specified artist name.
    2. func albumsByArtist(name string) ([]Album, error) {
    3. // An albums slice to hold data from returned rows.
    4. var albums []Album
    5. rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    6. if err != nil {
    7. return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    8. }
    9. defer rows.Close()
    10. // Loop through rows, using Scan to assign column data to struct fields.
    11. for rows.Next() {
    12. var alb Album
    13. if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
    14. return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    15. }
    16. albums = append(albums, alb)
    17. }
    18. if err := rows.Err(); err != nil {
    19. return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    20. }
    21. return albums, nil
    22. }

    このコードでは、次のことを行います:

    • 定義した Album 型の albums スライスを宣言します。これには、返された行からのデータが保持されます。構造体のフィールド名と型は、データベースの列名と型に対応します。
    • DB.Query を使用して、指定されたアーティスト名のアルバムをクエリする SELECT ステートメントを実行します。
      Query の最初のパラメータは SQL ステートメントです。パラメータの後に、任意の型のゼロまたはそれ以上のパラメータを渡すことができます。これにより、SQL ステートメントのパラメータの値を指定する場所が提供されます。SQL ステートメントとパラメータ値を分離することで(たとえば、fmt.Sprintf で連結するのではなく)、database/sql パッケージが SQL テキストとは別に値を送信できるようになり、SQL インジェクションのリスクが排除されます。
    • rows を閉じることを遅延させ、関数が終了する際に保持しているリソースが解放されるようにします。
    • 返された行をループし、Rows.Scan を使用して各行の列値を Album 構造体フィールドに割り当てます。
      Scan は、Go 値へのポインタのリストを受け取り、列値が書き込まれます。ここでは、alb 変数のフィールドへのポインタを渡し、& 演算子を使用して作成します。Scan はポインタを介して書き込み、構造体フィールドを更新します。
    • ループ内で、構造体フィールドに列値をスキャンする際のエラーをチェックします。
    • ループ内で、新しい albalbums スライスに追加します。
    • ループの後、rows.Err を使用して全体のクエリからのエラーをチェックします。クエリ自体が失敗した場合、ここでエラーをチェックすることが、結果が不完全であることを知る唯一の方法です。
  • 3. main 関数を更新して albumsByArtist を呼び出します。
    func main の末尾に、次のコードを追加します。
    1. albums, err := albumsByArtist("John Coltrane")
    2. if err != nil {
    3. log.Fatal(err)
    4. }
    5. fmt.Printf("Albums found: %v\n", albums)
    新しいコードでは、次のことを行います:
    • 追加した albumsByArtist 関数を呼び出し、その戻り値を新しい albums 変数に割り当てます。
    • 結果を出力します。

コードを実行する

main.go を含むディレクトリのコマンドラインから、コードを実行します。

  1. $ go run .
  2. Connected!
  3. Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]

次に、単一行をクエリします。

単一行をクエリする

このセクションでは、Go を使用してデータベース内の単一行をクエリします。

最大で単一行を返すことがわかっている SQL ステートメントには、QueryRow を使用できます。これは Query ループを使用するよりも簡単です。

コードを書く

  • 1. albumsByArtist の下に、次の albumByID 関数を貼り付けます。

    1. // albumByID queries for the album with the specified ID.
    2. func albumByID(id int64) (Album, error) {
    3. // An album to hold data from the returned row.
    4. var alb Album
    5. row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
    6. if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
    7. if err == sql.ErrNoRows {
    8. return alb, fmt.Errorf("albumsById %d: no such album", id)
    9. }
    10. return alb, fmt.Errorf("albumsById %d: %v", id, err)
    11. }
    12. return alb, nil
    13. }

    このコードでは、次のことを行います:

    • DB.QueryRow を使用して、指定された ID のアルバムをクエリする SELECT ステートメントを実行します。
      sql.Row を返します。呼び出しコード (あなたのコード!) を簡素化するために、QueryRow はエラーを返しません。代わりに、Rows.Scan から後でクエリエラー (たとえば sql.ErrNoRows) を返すように手配します。
    • Row.Scan を使用して、列値を構造体フィールドにコピーします。
    • Scan からエラーをチェックします。
      特別なエラー sql.ErrNoRows は、クエリが行を返さなかったことを示します。通常、このエラーは「そのようなアルバムはない」といったより具体的なテキストに置き換える価値があります。
  • 2. main を更新して albumByID を呼び出します。
    func main の末尾に、次のコードを追加します。
    1. // Hard-code ID 2 here to test the query.
    2. alb, err := albumByID(2)
    3. if err != nil {
    4. log.Fatal(err)
    5. }
    6. fmt.Printf("Album found: %v\n", alb)
    新しいコードでは、次のことを行います:
    • 追加した albumByID 関数を呼び出します。
    • 返されたアルバム ID を出力します。

コードを実行する

main.go を含むディレクトリのコマンドラインから、コードを実行します。

  1. $ go run .
  2. Connected!
  3. Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
  4. Album found: {2 Giant Steps John Coltrane 63.99}

次に、データベースにアルバムを追加します。

データを追加する

このセクションでは、Go を使用して SQL INSERT ステートメントを実行し、データベースに新しい行を追加します。

QueryQueryRow を使用してデータを返す SQL ステートメントを使用する方法を見てきました。データを返さない SQL ステートメントを実行するには、Exec を使用します。

コードを書く

  • 1. albumByID の下に、データベースに新しいアルバムを挿入するための次の addAlbum 関数を貼り付け、main.go を保存します。
    1. // addAlbum adds the specified album to the database,
    2. // returning the album ID of the new entry
    3. func addAlbum(alb Album) (int64, error) {
    4. result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    5. if err != nil {
    6. return 0, fmt.Errorf("addAlbum: %v", err)
    7. }
    8. id, err := result.LastInsertId()
    9. if err != nil {
    10. return 0, fmt.Errorf("addAlbum: %v", err)
    11. }
    12. return id, nil
    13. }
    このコードでは、次のことを行います:
    • DB.Exec を使用して INSERT ステートメントを実行します。
      Query と同様に、Exec は SQL ステートメントの後にパラメータ値を取ります。
    • INSERT の試行からエラーをチェックします。
    • Result.LastInsertId を使用して、挿入されたデータベース行の ID を取得します。
    • ID を取得しようとしたときのエラーをチェックします。
  • 2. main を更新して新しい addAlbum 関数を呼び出します。
    func main の末尾に、次のコードを追加します。
    1. albID, err := addAlbum(Album{
    2. Title: "The Modern Sound of Betty Carter",
    3. Artist: "Betty Carter",
    4. Price: 49.99,
    5. })
    6. if err != nil {
    7. log.Fatal(err)
    8. }
    9. fmt.Printf("ID of added album: %v\n", albID)
    新しいコードでは、次のことを行います:
    • 新しいアルバムを持つ addAlbum を呼び出し、追加しているアルバムの ID を albID 変数に割り当てます。

コードを実行する

main.go を含むディレクトリのコマンドラインから、コードを実行します。

  1. $ go run .
  2. Connected!
  3. Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
  4. Album found: {2 Giant Steps John Coltrane 63.99}
  5. ID of added album: 5

結論

おめでとうございます! Go を使用してリレーショナルデータベースで簡単な操作を実行しました。

次のトピックの提案:

  • ここで触れたトピックに関する詳細情報を含むデータアクセスガイドを確認してください。
  • Go に不慣れな場合は、Effective GoHow to write Go code に記載されている有用なベストプラクティスを見つけることができます。
  • Go Tour は、Go の基本を段階的に紹介する素晴らしい入門書です。

完成したコード

このセクションには、このチュートリアルで構築したアプリケーションのコードが含まれています。

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "log"
  6. "os"
  7. "github.com/go-sql-driver/mysql"
  8. )
  9. var db *sql.DB
  10. type Album struct {
  11. ID int64
  12. Title string
  13. Artist string
  14. Price float32
  15. }
  16. func main() {
  17. // Capture connection properties.
  18. cfg := mysql.Config{
  19. User: os.Getenv("DBUSER"),
  20. Passwd: os.Getenv("DBPASS"),
  21. Net: "tcp",
  22. Addr: "127.0.0.1:3306",
  23. DBName: "recordings",
  24. }
  25. // Get a database handle.
  26. var err error
  27. db, err = sql.Open("mysql", cfg.FormatDSN())
  28. if err != nil {
  29. log.Fatal(err)
  30. }
  31. pingErr := db.Ping()
  32. if pingErr != nil {
  33. log.Fatal(pingErr)
  34. }
  35. fmt.Println("Connected!")
  36. albums, err := albumsByArtist("John Coltrane")
  37. if err != nil {
  38. log.Fatal(err)
  39. }
  40. fmt.Printf("Albums found: %v\n", albums)
  41. // Hard-code ID 2 here to test the query.
  42. alb, err := albumByID(2)
  43. if err != nil {
  44. log.Fatal(err)
  45. }
  46. fmt.Printf("Album found: %v\n", alb)
  47. albID, err := addAlbum(Album{
  48. Title: "The Modern Sound of Betty Carter",
  49. Artist: "Betty Carter",
  50. Price: 49.99,
  51. })
  52. if err != nil {
  53. log.Fatal(err)
  54. }
  55. fmt.Printf("ID of added album: %v\n", albID)
  56. }
  57. // albumsByArtist queries for albums that have the specified artist name.
  58. func albumsByArtist(name string) ([]Album, error) {
  59. // An albums slice to hold data from returned rows.
  60. var albums []Album
  61. rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
  62. if err != nil {
  63. return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
  64. }
  65. defer rows.Close()
  66. // Loop through rows, using Scan to assign column data to struct fields.
  67. for rows.Next() {
  68. var alb Album
  69. if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
  70. return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
  71. }
  72. albums = append(albums, alb)
  73. }
  74. if err := rows.Err(); err != nil {
  75. return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
  76. }
  77. return albums, nil
  78. }
  79. // albumByID queries for the album with the specified ID.
  80. func albumByID(id int64) (Album, error) {
  81. // An album to hold data from the returned row.
  82. var alb Album
  83. row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
  84. if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
  85. if err == sql.ErrNoRows {
  86. return alb, fmt.Errorf("albumsById %d: no such album", id)
  87. }
  88. return alb, fmt.Errorf("albumsById %d: %v", id, err)
  89. }
  90. return alb, nil
  91. }
  92. // addAlbum adds the specified album to the database,
  93. // returning the album ID of the new entry
  94. func addAlbum(alb Album) (int64, error) {
  95. result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
  96. if err != nil {
  97. return 0, fmt.Errorf("addAlbum: %v", err)
  98. }
  99. id, err := result.LastInsertId()
  100. if err != nil {
  101. return 0, fmt.Errorf("addAlbum: %v", err)
  102. }
  103. return id, nil
  104. }