---
tags: Go
---
# GoでDatabaseを利用する
## インストール
### MySQL(Docker)のインストール
* 指定したディレクトリにsqlを置いて、開始時にテーブルを初期化できる
* docker-compose.yaml
```yaml
version: '3'
services:
db:
image: mysql:5.7
container_name: mysql_host
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
MYSQL_USER: user
MYSQL_PASSWORD:
TZ: 'Asia/Tokyo'
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- ./docker/db/data:/var/lib/mysql
- ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
- ./docker/db/sql:/docker-entrypoint-initdb.d
ports:
- 3306:3306
```
* ./docker/db/sql/init.sql
```sql
CREATE DATABASE testdb;
CREATE TABLE `test_table`
(
id INT(20) AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
PRIMARY KEY (`id`)
)
```
### 起動
* docker-compose起動
```bash
$ docker-compose up
```
### 接続
* MySQLクライアントをインストールする
```bash
$ brew intall mysql-client
```
* mysql-clientで接続する
```bash
$ mysql -uroot -p'root' -h 127.0.0.1
```
## database/sqlパッケージ
### sql.Open()
* sql.OpenにDB名と、接続先を指定して接続を行う
* 初期化を行った後は、疎通チェックのため、`Ping()`を呼ぶ
```go
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"log"
)
func main() {
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
if err != nil {
log.Fatalln(err)
}
ping(db)
}
func ping(db *sql.DB) {
if err := db.Ping(); err != nil {
log.Fatalln(err)
}
}
```
#### importについて
* mysqlのDBドライバは、直接指定しないがimportを行う必要がある
* それぞれのパッケージ内で、init()関数でドライバが追加される
* init()関数はmain()関数より前に呼び出される関数
* 例: https://github.com/go-sql-driver/mysql/blob/v1.4/driver.go#L171
```go
func init() {
sql.Register("mysql", &MySQLDriver{})
}
```
* importの指定を忘れると以下のようなエラーが返る
```
sql: unknown driver "mysql" (forgotten import?)
```
### db.Exec()
* UPDATEやDELETEなど、更新のクエリを実行する際に使う
```go
func (s *Stmt) Exec(args ...interface{}) (Result, error)
```
* Example: INSERTを行う場合
```go
result, err = db.Exec("INSERT INTO test_table (name) VALUES ('sato')")
```
### db.Query()
* SELECTなどの参照のクエリを実行する際に使う
```go
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
```
* `Rows.Next()`で結果をイテレーション、`Rows.Close()`でRowsを閉じる
```go
rows, err := db.Query("SELECT id, name FROM test_table")
if err != nil {
log.Fatalln(err)
}
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatalf("cannot scan: %s", err)
}
fmt.Printf("id: %d, name: %s", id, name)
}
if err := rows.Close(); err != nil {
log.Fatalln(err)
}
```
### db.Stats()
* DBの統計情報を取得する
```go
fmt.Printf("%+v", db.Stats())
{MaxOpenConnections:0 OpenConnections:1 InUse:0 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:0 MaxLifetimeClosed:0}
```
### db.Prepare()
* プリペアドステートメントのステートメント`Stmt`が生成される
* プリペアドステートメントについて
* 処理の流れ
1. プレースホルダ付きのSQLをサーバーに送る
2. サーバーからステートメントIDが返る
3. クライアントはIDとパラメータを送り実行
* SQLのパースは比較的時間がかかるため、プリペアドステートメントによりパフォーマンスが上がる
* Example
```go
stmt, err := db.Prepare("INSERT INTO test_table (id, name) VALUES(?, ?)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
for id, name := range names {
if _, err := stmt.Exec(id+1, name); err != nil {
log.Fatal(err)
}
}
```
### Transaction
* db.Begin()
* トランザクションを開始することができる
* 戻り値`Tx`はトランザクションを表す
* tx.Rollback()
* ロールバックを行う
* tx.Commit()
* トランザクションが完了する
* 実行例
```go
tx, err := db.Begin()
result, err := tx.Exec("INSERT INTO test_table (name) VALUES ('sato')")
if err != nil {
// error handling
}
if err := failedSomething(); err != nil {
if err := tx.Rollback(); err != nil {
// error handling
}
}
tx.Commit()
```
## [sqlx](https://github.com/jmoiron/sqlx)
* database/sqlの拡張パッケージ
* sqlの結果を構造体にMarshalできる
* 基本的な使い方は、database/sqlとほぼ同じ
### [db.Get()](https://godoc.org/github.com/jmoiron/sqlx#DB.Get)
* 引数
* 第1引数: 結果を格納する変数のポインタ
* 第2引数: sql
* 第3引数以降: プレースホルダへ埋め込む変数
* ゼロ件だった場合はエラーとなる
* サンプル
```go
p := Place{}
err = db.Get(&p, "SELECT * FROM place LIMIT 1")
```
### [db.Select()](https://godoc.org/github.com/jmoiron/sqlx#DB.Select)
* db.Get()とほぼ同様だが、結果が複数件になる場合利用する
## Unit Test
TODO
## 参考
- https://blog.withnic.net/2018/08/golang%E3%81%A7transaction%E5%87%A6%E7%90%86%E3%82%92%E8%80%83%E3%81%88%E3%82%8B/