Webアプリケーションは、データベースにデータを保存したり、データベースからデータを取得したりする必要があります。これは、動的なコンテンツを扱ったり、ユーザーがデータを入力するためのフォームを提供したり、ユーザーを認証するためのログインとパスワードの資格情報を保存したりする場合に、ほぼ常に当てはまります。そのためには、データベースが必要です。
データベースには、あらゆる種類があります。Web全体で一般的に使用されているデータベースの1つは、MySQLデータベースです。MySQLは長い間存在しており、その地位と安定性を何度も証明してきました。
この例では、Goでのデータベースアクセスの基礎について説明し、データベーステーブルの作成、データの保存、データの取得について説明します。
go-sql-driver/mysql
パッケージのインストールGoプログラミング言語には、あらゆる種類のSQLデータベースをクエリするための便利なパッケージである`database/sql`が付属しています。これは、すべての一般的なSQL機能を単一のAPIに抽象化して使用できるため便利です。Goには含まれていないのは、データベースドライバです。Goでは、データベースドライバは、特定のデータベース(この場合はMySQL)の低レベルの詳細を実装するパッケージです。すでにお分かりかもしれませんが、これは前方互換性を維持するために役立ちます。すべてのGoパッケージを作成する時点で、作成者は将来登場するすべてのデータベースを予測することはできず、考えられるすべてのデータベースをサポートすることは、膨大な量のメンテナンス作業になります。
MySQLデータベースドライバをインストールするには、任意のターミナルに移動して、次のコマンドを実行します。
go get -u github.com/go-sql-driver/mysql
必要なすべてのパッケージをインストールした後、最初に確認する必要があるのは、MySQLデータベースに正常に接続できるかどうかです。MySQLデータベースサーバーがまだ実行されていない場合は、Dockerを使用して新しいインスタンスを簡単に起動できます。Docker MySQLイメージの公式ドキュメントは次のとおりです:https://hub.docker.com/_/mysql
データベースに接続できるかどうかを確認するには、database/sql
パッケージとgo-sql-driver/mysql
パッケージをインポートし、次のように接続を開きます。import "database/sql"
import _ "go-sql-driver/mysql"
// Configure the database connection (always check errors)
db, err := sql.Open("mysql", "username:password@(127.0.0.1:3306)/dbname?parseTime=true")
// Initialize the first connection to the database, to see if everything works correctly.
// Make sure to check the error.
err := db.Ping()
データベースのすべてのデータエントリは、特定のテーブルに格納されます。データベーステーブルは、列と行で構成されます。列は、各データエントリにラベルを付け、そのタイプを指定します。行は、挿入されたデータ値です。最初の例では、次のようなテーブルを作成します。
id | ユーザー名 | パスワード | 作成日時 |
---|---|---|---|
1 | johndoe | secret | 2019-08-10 12:30:00 |
SQLに変換すると、テーブルを作成するためのコマンドは次のようになります。
CREATE TABLE users (
id INT AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (id)
);
SQLコマンドができたので、database/sql
パッケージを使用して、MySQLデータベースにテーブルを作成できます。
query := `
CREATE TABLE users (
id INT AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (id)
);`
// Executes the SQL query in our database. Check err to ensure there was no error.
_, err := db.Exec(query)
SQLに精通している場合、新しいデータをテーブルに挿入することは、テーブルを作成するのと同じくらい簡単です。注意すべき点の1つは、デフォルトでは、GoはSQLクエリに動的データを挿入するためにプリペアドステートメントを使用することです。これは、損傷のリスクなしに、ユーザーが提供したデータをデータベースに安全に渡す方法です。Webプログラミングの初期の頃、プログラマーはクエリとともにデータをデータベースに直接渡していました。これは、大規模な脆弱性を引き起こし、Webアプリケーション全体を破壊する可能性がありました。そうしないでください。正しくするのは簡単です。
最初のユーザーをデータベーステーブルに挿入するには、次のようなSQLクエリを作成します。ご覧のとおり、id列は省略しています。これは、MySQLによって自動的に設定されるためです。疑問符は、SQLドライバに対して、実際のデータのプレースホルダーであることを示しています。これは、前述したプリペアドステートメントを確認できる場所です。INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)
これで、GoでこのSQLクエリを使用して、テーブルに新しい行を挿入できます。
import "time"
username := "johndoe"
password := "secret"
createdAt := time.Now()
// Inserts our data into the users table and returns with the result and a possible error.
// The result contains information about the last inserted id (which was auto-generated for us) and the count of rows this query affected.
result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)
ユーザーに新しく作成されたIDを取得するには、次のように取得します。
userID, err := result.LastInsertId()
テーブルにユーザーができたので、それをクエリしてすべての情報を取得します。Goでは、テーブルをクエリする方法は2つあります。反復処理するために複数の行をクエリできるdb.Query
と、特定の行のみをクエリする場合のdb.QueryRow
があります。
SELECT id, username, password, created_at FROM users WHERE id = ?
Goでは、最初にデータを格納する変数を宣言し、次のように単一のデータベース行をクエリします。
var (
id int
username string
password string
createdAt time.Time
)
// Query the database and scan the values into out variables. Don't forget to check for errors.
query := `SELECT id, username, password, created_at FROM users WHERE id = ?`
err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt)
前のセクションでは、単一のユーザー行をクエリする方法について説明しました。多くのアプリケーションには、既存のすべてのユーザーをクエリするユースケースがあります。これは上記の例と似ていますが、もう少しコーディングが必要です。
上記の例のSQLコマンドを使用して、WHERE
句を切り取ることができます。こうすると、既存のすべてのユーザーがクエリされます。
SELECT id, username, password, created_at FROM users
Goでは、最初にデータを格納する変数を宣言し、次のように単一のデータベース行をクエリします。
type user struct {
id int
username string
password string
createdAt time.Time
}
rows, err := db.Query(`SELECT id, username, password, created_at FROM users`) // check err
defer rows.Close()
var users []user
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt) // check err
users = append(users, u)
}
err := rows.Err() // check err
usersスライスには、次のようなものが含まれている場合があります。
users {
user {
id: 1,
username: "johndoe",
password: "secret",
createdAt: time.Time{wall: 0x0, ext: 63701044325, loc: (*time.Location)(nil)},
},
user {
id: 2,
username: "alice",
password: "bob",
createdAt: time.Time{wall: 0x0, ext: 63701044622, loc: (*time.Location)(nil)},
},
}
最後に、テーブルからユーザーを削除することは、上記のセクションの.Exec
と同じくらい簡単です。
_, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1) // check err
これは、この例で学習したことを試すために使用できる完全なコードです。
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "root:root@(127.0.0.1:3306)/root?parseTime=true")
if err != nil {
log.Fatal(err)
}
if err := db.Ping(); err != nil {
log.Fatal(err)
}
{ // Create a new table
query := `
CREATE TABLE users (
id INT AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (id)
);`
if _, err := db.Exec(query); err != nil {
log.Fatal(err)
}
}
{ // Insert a new user
username := "johndoe"
password := "secret"
createdAt := time.Now()
result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)
if err != nil {
log.Fatal(err)
}
id, err := result.LastInsertId()
fmt.Println(id)
}
{ // Query a single user
var (
id int
username string
password string
createdAt time.Time
)
query := "SELECT id, username, password, created_at FROM users WHERE id = ?"
if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {
log.Fatal(err)
}
fmt.Println(id, username, password, createdAt)
}
{ // Query all users
type user struct {
id int
username string
password string
createdAt time.Time
}
rows, err := db.Query(`SELECT id, username, password, created_at FROM users`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var users []user
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt)
if err != nil {
log.Fatal(err)
}
users = append(users, u)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("%#v", users)
}
{
_, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1)
if err != nil {
log.Fatal(err)
}
}
}