2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go言語の`database/sql`でSQLiteを操作する【初心者向け】

2
Posted at

はじめに

Go言語にはデータベース操作のための標準ライブラリdatabase/sqlが用意されています。

ORMフレームワーク(GORMなど)を使わずに生SQLを書くことで、以下のメリットがあります。

  • SQLの理解が深まる
  • 処理速度が速い
  • 外部依存を最小限に抑えられる

この記事では、database/sql + SQLiteを使ったCRUD操作を一から解説します。


環境

項目 バージョン
Go 1.21以上
SQLite ドライバー github.com/mattn/go-sqlite3

1. プロジェクトのセットアップ

mkdir go-sqlite-sample
cd go-sqlite-sample
go mod init go-sqlite-sample
go get github.com/mattn/go-sqlite3

go-sqlite3はSQLiteのCバインディングです。インストール時にCコンパイラ(GCC)が必要です。Windowsの場合はTDM-GCC等を事前にインストールしてください。


2. database/sqlの構成を理解する

Go標準ライブラリ
└── database/sql  ← DB操作の窓口(インターフェース)
        ↓
ドライバー(DB種別ごとに1つだけ外部パッケージが必要)
└── github.com/mattn/go-sqlite3  ← SQLite用ドライバー

database/sqlはGoの標準ライブラリです。DB種別ごとのドライバーを差し替えるだけで、MySQLやPostgreSQLにも同じ書き方で対応できます。


3. DB接続

package main

import (
    "database/sql"
    "log"

    _ "github.com/mattn/go-sqlite3" // 副作用インポート
)

func main() {
    db, err := sql.Open("sqlite3", "./sample.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
}

_ "github.com/mattn/go-sqlite3"_副作用インポートと呼ばれます。直接使用しないが、ドライバーをGo内部に登録するために必要な記述です。省略するとコンパイルエラーになります。


4. テーブル作成

_, err = db.Exec(`
    CREATE TABLE IF NOT EXISTS users (
        id         INTEGER PRIMARY KEY AUTOINCREMENT,
        name       TEXT    NOT NULL,
        email      TEXT    UNIQUE NOT NULL,
        age        INTEGER,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
`)
if err != nil {
    log.Fatal(err)
}

IF NOT EXISTSをつけることで、テーブルが既に存在してもエラーにならずに済みます。


5. CRUD操作

5-1. Create(レコード追加)

_, err = db.Exec(
    "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
    "田中太郎", "taro@example.com", 27,
)
if err != nil {
    log.Fatal(err)
}

プレースホルダー(?)を必ず使う

値を文字列で直接埋め込む書き方("... VALUES ('" + name + "', ...")はSQLインジェクションの危険があります。?を使うことで安全にクエリを実行できます。


5-2. Read(レコード取得)

複数件取得

rows, err := db.Query("SELECT id, name, email, age FROM users")
if err != nil {
    log.Fatal(err)
}
defer rows.Close() // 必ずCloseする

for rows.Next() {
    var id, age int
    var name, email string

    err := rows.Scan(&id, &name, &email, &age)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID:%d Name:%s Email:%s Age:%d\n", id, name, email, age)
}

// rows.Next()後のエラーチェックも忘れずに
if err = rows.Err(); err != nil {
    log.Fatal(err)
}

1件取得

var name string
var age int

err = db.QueryRow(
    "SELECT name, age FROM users WHERE id = ?", 1,
).Scan(&name, &age)

if err == sql.ErrNoRows {
    fmt.Println("レコードが見つかりませんでした")
} else if err != nil {
    log.Fatal(err)
}

fmt.Printf("Name:%s Age:%d\n", name, age)

QueryRowは該当レコードがない場合にsql.ErrNoRowsを返します。err != nilだけでなく、sql.ErrNoRowsのチェックを必ず行いましょう。


5-3. Update(レコード更新)

result, err := db.Exec(
    "UPDATE users SET age = ? WHERE id = ?",
    28, 1,
)
if err != nil {
    log.Fatal(err)
}

// 更新件数の確認
rowsAffected, _ := result.RowsAffected()
fmt.Printf("更新件数: %d件\n", rowsAffected)

5-4. Delete(レコード削除)

result, err := db.Exec("DELETE FROM users WHERE id = ?", 1)
if err != nil {
    log.Fatal(err)
}

rowsAffected, _ := result.RowsAffected()
fmt.Printf("削除件数: %d件\n", rowsAffected)

6. トランザクション

複数の操作をまとめて行い、すべて成功したときだけコミットする仕組みです。

// トランザクション開始
tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}

// エラー時は自動ロールバック
defer func() {
    if err != nil {
        tx.Rollback()
    }
}()

// トランザクション内での操作
_, err = tx.Exec(
    "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
    "テストユーザー", "test@example.com", 30,
)
if err != nil {
    return // deferでRollbackが実行される
}

_, err = tx.Exec(
    "UPDATE users SET age = ? WHERE name = ?",
    31, "テストユーザー",
)
if err != nil {
    return // deferでRollbackが実行される
}

// すべて成功したらコミット
err = tx.Commit()
if err != nil {
    log.Fatal(err)
}

fmt.Println("トランザクション完了")

7. よく使うメソッドの使い分け

メソッド 用途 戻り値
db.Exec() INSERT / UPDATE / DELETE Result, error
db.Query() 複数行のSELECT *Rows, error
db.QueryRow() 1行のSELECT *Row
db.Begin() トランザクション開始 *Tx, error

8. 全体コード

package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

func main() {
    // DB接続
    db, err := sql.Open("sqlite3", "./sample.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // テーブル作成
    _, err = db.Exec(`
        CREATE TABLE IF NOT EXISTS users (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            name       TEXT    NOT NULL,
            email      TEXT    UNIQUE NOT NULL,
            age        INTEGER,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    `)
    if err != nil {
        log.Fatal(err)
    }

    // INSERT
    _, err = db.Exec(
        "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
        "田中太郎", "taro@example.com", 27,
    )
    if err != nil {
        log.Fatal(err)
    }

    // SELECT(全件)
    rows, err := db.Query("SELECT id, name, email, age FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id, age int
        var name, email string
        rows.Scan(&id, &name, &email, &age)
        fmt.Printf("ID:%d Name:%s Email:%s Age:%d\n", id, name, email, age)
    }

    // UPDATE
    db.Exec("UPDATE users SET age = ? WHERE id = ?", 28, 1)

    // DELETE
    db.Exec("DELETE FROM users WHERE id = ?", 1)

    fmt.Println("完了")
}

まとめ

操作 使うメソッド ポイント
INSERT / UPDATE / DELETE db.Exec() プレースホルダー?を使う
複数件SELECT db.Query() + rows.Next() defer rows.Close()を忘れずに
1件SELECT db.QueryRow() sql.ErrNoRowsのチェックを行う
まとめて操作 db.Begin()tx.Commit() エラー時はtx.Rollback()

さいごに

database/sqlは生SQLを書くため、最初は少し冗長に感じるかもしれません。しかしSQLの理解が直接コードに反映されるため、データベースの学習と実装を同時に進めることができます。

GORMなどのORMに移行する場合も、database/sqlの知識が土台になります。まずは生SQLで基礎を固めることをおすすめします。


参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?