前提条件
- MySQL リレーショナルデータベース管理システム (DBMS) のインストール。
- Go のインストール。 インストール手順については、Installing Go を参照してください。
- コードを編集するためのツール。 お使いのテキストエディタで問題ありません。
- コマンドターミナル。 Go は、Linux や Mac の任意のターミナル、または Windows の PowerShell や cmd でうまく動作します。
コード用のフォルダーを作成する
まず、作成するコード用のフォルダーを作成します。
- 1. コマンドプロンプトを開き、ホームディレクトリに移動します。
Linux または Mac の場合:
Windows の場合:$ cd
チュートリアルの残りの部分では、プロンプトとして $ を表示します。使用するコマンドは Windows でも動作します。C:\> cd %HOMEPATH%
- 2. コマンドプロンプトから、コード用のディレクトリを data-access という名前で作成します。
$ mkdir data-access
$ cd data-access
- 3. このチュートリアル中に追加する依存関係を管理するためのモジュールを作成します。
go mod init
コマンドを実行し、新しいコードのモジュールパスを指定します。
このコマンドは、追加した依存関係が追跡される go.mod ファイルを作成します。詳細については、Managing dependencies を必ず参照してください。$ go mod init example/data-access
go: creating new go.mod: module example/data-access
注意: 実際の開発では、より具体的なニーズに応じたモジュールパスを指定します。詳細については、Managing dependencies を参照してください。
データベースを設定する
このステップでは、作業するデータベースを作成します。データベースとテーブルを作成し、データを追加するために、DBMS 自体の CLI を使用します。
ビンテージジャズレコーディングに関するデータを持つデータベースを作成します。
ここでのコードは MySQL CLI を使用していますが、ほとんどの DBMS には同様の機能を持つ独自の CLI があります。
- 1. 新しいコマンドプロンプトを開きます。
2. コマンドラインで、DBMS にログインします。以下は MySQL の例です。
$ mysql -u root -p
Enter password:
mysql>
- 3.
mysql
コマンドプロンプトで、データベースを作成します。mysql> create database recordings;
- 4. 作成したばかりのデータベースに切り替えて、テーブルを追加できるようにします。
mysql> use recordings;
Database changed
- 5. テキストエディタで、data-access フォルダー内に create-tables.sql という名前のファイルを作成し、テーブルを追加するための SQL スクリプトを保持します。
6. ファイルに次の SQL コードを貼り付けて、ファイルを保存します。
DROP TABLE IF EXISTS album;
CREATE TABLE album (
id INT AUTO_INCREMENT NOT NULL,
title VARCHAR(128) NOT NULL,
artist VARCHAR(255) NOT NULL,
price DECIMAL(5,2) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO album
(title, artist, price)
VALUES
('Blue Train', 'John Coltrane', 56.99),
('Giant Steps', 'John Coltrane', 63.99),
('Jeru', 'Gerry Mulligan', 17.99),
('Sarah Vaughan', 'Sarah Vaughan', 34.98);
この SQL コードでは、次のことを行います:
album
という名前のテーブルを削除 (ドロップ) します。このコマンドを最初に実行することで、テーブルを再作成したい場合にスクリプトを再実行しやすくなります。album
テーブルを作成し、4 つの列を持ちます:title
、artist
、price
。各行のid
値は DBMS によって自動的に作成されます。- 値を持つ 4 行を追加します。
- 7.
mysql
コマンドプロンプトから、作成したスクリプトを実行します。
次の形式でsource
コマンドを使用します:mysql> source /path/to/create-tables.sql
- 8. DBMS コマンドプロンプトで、
SELECT
ステートメントを使用して、データを持つテーブルが正常に作成されたことを確認します。mysql> select * from album;
+----+---------------+----------------+-------+
| id | title | artist | price |
+----+---------------+----------------+-------+
| 1 | Blue Train | John Coltrane | 56.99 |
| 2 | Giant Steps | John Coltrane | 63.99 |
| 3 | Jeru | Gerry Mulligan | 17.99 |
| 4 | Sarah Vaughan | Sarah Vaughan | 34.98 |
+----+---------------+----------------+-------+
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 に、ドライバーパッケージをインポートするための次のコードを貼り付けます。
package main
import "github.com/go-sql-driver/mysql"
このコードでは、次のことを行います:
- コードを
main
パッケージに追加して、独立して実行できるようにします。 - MySQL ドライバー
github.com/go-sql-driver/mysql
をインポートします。
ドライバーがインポートされたので、データベースにアクセスするためのコードを書き始めます。
データベースハンドルを取得して接続する
データベースハンドルを使用してデータベースアクセスを提供する Go コードを書きます。
特定のデータベースへのアクセスを表す sql.DB
構造体へのポインタを使用します。
コードを書く
1. main.go の
import
コードの下に、データベースハンドルを作成するための次の Go コードを貼り付けます。var db *sql.DB
func main() {
// Capture connection properties.
cfg := mysql.Config{
User: os.Getenv("DBUSER"),
Passwd: os.Getenv("DBPASS"),
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "recordings",
}
// Get a database handle.
var err error
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected!")
}
このコードでは、次のことを行います:
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 ファイルの先頭近く、パッケージ宣言のすぐ下に、書いたコードをサポートするために必要なパッケージをインポートします。
ファイルの先頭は次のようになります:package main
import (
"database/sql"
"fmt"
"log"
"os"
"github.com/go-sql-driver/mysql"
)
- 3. main.go を保存します。
コードを実行する
- 1. MySQL ドライバーモジュールを依存関係として追跡し始めます。
go get
を使用して、github.com/go-sql-driver/mysql モジュールを自分のモジュールの依存関係として追加します。ドット引数を使用して「現在のディレクトリのコードの依存関係を取得する」ことを意味します。
Go は、前のステップで$ go get .
go get: added github.com/go-sql-driver/mysql v1.6.0
import
宣言に追加したため、この依存関係をダウンロードしました。依存関係の追跡についての詳細は、Adding a dependency を参照してください。 - 2. コマンドプロンプトから、Go プログラムで使用する
DBUSER
とDBPASS
環境変数を設定します。
Linux または Mac の場合:
Windows の場合:$ export DBUSER=username
$ export DBPASS=password
C:\Users\you\data-access> set DBUSER=username
C:\Users\you\data-access> set DBPASS=password
- 3. main.go を含むディレクトリのコマンドラインから、
go run
を入力してコードを実行します。ドット引数を使用して「現在のディレクトリのパッケージを実行する」ことを意味します。$ go run .
Connected!
複数行をクエリする
このセクションでは、Go を使用して複数行を返すように設計された SQL クエリを実行します。
複数行を返す可能性のある SQL ステートメントには、Query
メソッドを使用し、database/sql
パッケージから返された行をループします。(後で、Query for a single row セクションで、単一行をクエリする方法を学びます。)
コードを書く
- 1. main.go の
func main
のすぐ上に、Album
構造体の定義を貼り付けます。これを使用して、クエリから返された行データを保持します。type Album struct {
ID int64
Title string
Artist string
Price float32
}
2.
func main
の下に、データベースをクエリするための次のalbumsByArtist
関数を貼り付けます。// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
// An albums slice to hold data from returned rows.
var albums []Album
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
defer rows.Close()
// Loop through rows, using Scan to assign column data to struct fields.
for rows.Next() {
var alb Album
if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
albums = append(albums, alb)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
return albums, nil
}
このコードでは、次のことを行います:
- 定義した
Album
型のalbums
スライスを宣言します。これには、返された行からのデータが保持されます。構造体のフィールド名と型は、データベースの列名と型に対応します。 DB.Query
を使用して、指定されたアーティスト名のアルバムをクエリするSELECT
ステートメントを実行します。Query
の最初のパラメータは SQL ステートメントです。パラメータの後に、任意の型のゼロまたはそれ以上のパラメータを渡すことができます。これにより、SQL ステートメントのパラメータの値を指定する場所が提供されます。SQL ステートメントとパラメータ値を分離することで(たとえば、fmt.Sprintf
で連結するのではなく)、database/sql
パッケージが SQL テキストとは別に値を送信できるようになり、SQL インジェクションのリスクが排除されます。rows
を閉じることを遅延させ、関数が終了する際に保持しているリソースが解放されるようにします。- 返された行をループし、
Rows.Scan
を使用して各行の列値をAlbum
構造体フィールドに割り当てます。Scan
は、Go 値へのポインタのリストを受け取り、列値が書き込まれます。ここでは、alb
変数のフィールドへのポインタを渡し、&
演算子を使用して作成します。Scan
はポインタを介して書き込み、構造体フィールドを更新します。 - ループ内で、構造体フィールドに列値をスキャンする際のエラーをチェックします。
- ループ内で、新しい
alb
をalbums
スライスに追加します。 - ループの後、
rows.Err
を使用して全体のクエリからのエラーをチェックします。クエリ自体が失敗した場合、ここでエラーをチェックすることが、結果が不完全であることを知る唯一の方法です。
- 3.
main
関数を更新してalbumsByArtist
を呼び出します。func main
の末尾に、次のコードを追加します。
新しいコードでは、次のことを行います:albums, err := albumsByArtist("John Coltrane")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)
コードを実行する
main.go を含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
単一行をクエリする
このセクションでは、Go を使用してデータベース内の単一行をクエリします。
最大で単一行を返すことがわかっている SQL ステートメントには、QueryRow
を使用できます。これは Query
ループを使用するよりも簡単です。
コードを書く
1.
albumsByArtist
の下に、次のalbumByID
関数を貼り付けます。// albumByID queries for the album with the specified ID.
func albumByID(id int64) (Album, error) {
// An album to hold data from the returned row.
var alb Album
row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
if err == sql.ErrNoRows {
return alb, fmt.Errorf("albumsById %d: no such album", id)
}
return alb, fmt.Errorf("albumsById %d: %v", id, err)
}
return alb, nil
}
このコードでは、次のことを行います:
DB.QueryRow
を使用して、指定された ID のアルバムをクエリするSELECT
ステートメントを実行します。sql.Row
を返します。呼び出しコード (あなたのコード!) を簡素化するために、QueryRow
はエラーを返しません。代わりに、Rows.Scan
から後でクエリエラー (たとえばsql.ErrNoRows
) を返すように手配します。Row.Scan
を使用して、列値を構造体フィールドにコピーします。Scan
からエラーをチェックします。
特別なエラーsql.ErrNoRows
は、クエリが行を返さなかったことを示します。通常、このエラーは「そのようなアルバムはない」といったより具体的なテキストに置き換える価値があります。
- 2.
main
を更新してalbumByID
を呼び出します。func main
の末尾に、次のコードを追加します。
新しいコードでは、次のことを行います:// Hard-code ID 2 here to test the query.
alb, err := albumByID(2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Album found: %v\n", alb)
コードを実行する
main.go を含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}
データを追加する
このセクションでは、Go を使用して SQL INSERT
ステートメントを実行し、データベースに新しい行を追加します。
Query
と QueryRow
を使用してデータを返す SQL ステートメントを使用する方法を見てきました。データを返さない SQL ステートメントを実行するには、Exec
を使用します。
コードを書く
- 1.
albumByID
の下に、データベースに新しいアルバムを挿入するための次のaddAlbum
関数を貼り付け、main.go を保存します。
このコードでは、次のことを行います:// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
return id, nil
}
DB.Exec
を使用してINSERT
ステートメントを実行します。Query
と同様に、Exec
は SQL ステートメントの後にパラメータ値を取ります。INSERT
の試行からエラーをチェックします。Result.LastInsertId
を使用して、挿入されたデータベース行の ID を取得します。- ID を取得しようとしたときのエラーをチェックします。
- 2.
main
を更新して新しいaddAlbum
関数を呼び出します。func main
の末尾に、次のコードを追加します。
新しいコードでは、次のことを行います:albID, err := addAlbum(Album{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)
コードを実行する
main.go を含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}
ID of added album: 5
結論
おめでとうございます! Go を使用してリレーショナルデータベースで簡単な操作を実行しました。
次のトピックの提案:
- ここで触れたトピックに関する詳細情報を含むデータアクセスガイドを確認してください。
- Go に不慣れな場合は、Effective Go や How to write Go code に記載されている有用なベストプラクティスを見つけることができます。
- Go Tour は、Go の基本を段階的に紹介する素晴らしい入門書です。
完成したコード
このセクションには、このチュートリアルで構築したアプリケーションのコードが含まれています。
package main
import (
"database/sql"
"fmt"
"log"
"os"
"github.com/go-sql-driver/mysql"
)
var db *sql.DB
type Album struct {
ID int64
Title string
Artist string
Price float32
}
func main() {
// Capture connection properties.
cfg := mysql.Config{
User: os.Getenv("DBUSER"),
Passwd: os.Getenv("DBPASS"),
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "recordings",
}
// Get a database handle.
var err error
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected!")
albums, err := albumsByArtist("John Coltrane")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)
// Hard-code ID 2 here to test the query.
alb, err := albumByID(2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Album found: %v\n", alb)
albID, err := addAlbum(Album{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)
}
// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
// An albums slice to hold data from returned rows.
var albums []Album
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
defer rows.Close()
// Loop through rows, using Scan to assign column data to struct fields.
for rows.Next() {
var alb Album
if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
albums = append(albums, alb)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
return albums, nil
}
// albumByID queries for the album with the specified ID.
func albumByID(id int64) (Album, error) {
// An album to hold data from the returned row.
var alb Album
row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
if err == sql.ErrNoRows {
return alb, fmt.Errorf("albumsById %d: no such album", id)
}
return alb, fmt.Errorf("albumsById %d: %v", id, err)
}
return alb, nil
}
// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
return id, nil
}