MySQLデータベース

こんにちは、Philippです!
私のプラットフォームであるGo Web Examples Coursesが開始されたことをお知らせします。GoでのWeb開発に関する分かりやすいビデオコースをお楽しみください。早期サポーター向けの特別オファーをぜひチェックしてください。
そちらでお会いしましょう! :)
詳細はこちら

はじめに

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データベースに正常に接続できるかどうかです。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があります。

特定の行のクエリは、基本的にこれまでに説明した他のすべてのSQLコマンドと同じように機能します。
IDで1人のユーザーをクエリするSQLコマンドは次のようになります。

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)
        }
    }
}