22
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NRI OpenStandia Advent Calendar 2022

Day 3

sijms/go-oraを使ってOracleDBを操作してみる (Golang)

Posted at

Go言語を学び始めて1ヶ月の私が、Oracle DBのCRUD処理を実装する必要があったので、記事を書いてみようと思います。

1. はじめに

1.1. 読んでみてほしい対象者

  • Go言語でOracle DB接続を実装したい人
  • OR Mapperを利用できないため、簡易的にDB処理を実装したい人

1.2 利用するOracle Driver

今回利用するOracle Driverはsijms/go-oraです。

sijms/go-oraはv1とv2がありますが、v2を利用します。
(Oracle 10.2以降はv2の利用を推奨と公式でも書かれています。)

私が利用を検討した結果のsijms/go-oraのメリット/デメリットは下記です。

  • メリット
    • 導入が簡単
    • コンテナのイメージサイズ軽量化
      • 他のOracle Driverの場合、Oracle Instant Clientが必要なため、コンテナ化するとイメージサイズが大きくなってしまいます。
      • 例 : 「sijms/go-ora」と「mattn/go-oci8」のコンテナイメージサイズの比較
        • sijms/go-ora: 13.9MB
        • mattn/go-oci8: 1.5GB
  • デメリット
    • OR Mapperが使えない。
      • xormgormsijms/go-oraは対応していないです。(2022/12/03 時点)

1.3. 環境情報

環境 バージョン
OS Windows10 Home 21H2
Golang 19.1
Oracle 19c(19.3.0)

1.4. 動作確認するテーブル情報

下記のような商品を模したテーブルを作成して、OracleDBの動作確認をしてみます。

CREATE TABLE items (
  id VARCHAR2(3) NOT NULL,
  name VARCHAR2(10) NOT NULL,
  price INT NOT NULL,
  description VARCHAR2(20),
  constraint items_pk primary key (id)
)

2. Entity定義編

構造体(struct)を用いてEntity定義を行います。

NOT NULL制約のある項目は、Go言語標準の型(int, string, etc..)を利用すれば良いのですが、
NOT NULL制約のない項目は、database/sqlで用意された型(NullInt32, NullString, etc..)を利用しましょう。
(database/sqlの型を利用しないと、nullをSELECTした時などにエラーが起きます。)

type Item struct {
	Id          string
	Name        string
	Price       int
	Description sql.NullString
}

3. DB Open + DML編

3.1. DB Open

sql.Open()関数を利用して、DBをオープンします。
DataSourceNameは、go-oraで用意されたBuildUrl()関数があるので、これを利用すると実装が楽です。

package main

import (
	"database/sql"
	"log"

	go_ora "github.com/sijms/go-ora/v2"
)

func main() {
	db, err := open()
	if err != nil {
		return
	}
	defer db.Close()
}
func open() (*sql.DB, error) {
	dataSourceName := go_ora.BuildUrl("localhost", 1521, "SAMPLEPDB", "username", "password", nil)
	db, err := sql.Open("oracle", dataSourceName)
	if err != nil {
		log.Fatalln("error:", err)
		return nil, err
	}
	return db, err
}

3.2. Insert

sql.DBのExec()関数を利用して、SQLを実行します。

type Item struct { /* 省略 */ }

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	item := Item{Id: "001", Name: "pen", Price: 100, Description: sql.NullString{String: "Color is black.", Valid: true}}
	if err := insert(db, item); err != nil {
		log.Fatalln("Insert Error:", err)
		return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func insert(db *sql.DB, item Item) error {
	_, err := db.Exec(
		"INSERT INTO items (id, name, price, description) VALUES (:1, :2, :3, :4)",
		item.Id, item.Name, item.Price, item.Description.String,
	)
	return err
}

実行後のDB結果

ID  NAME            PRICE DESCRIPTION
--- ---------- ---------- --------------------
001 pen               100 Color is black.

3.3. Select

sql.DBのQuery()関数を利用して、SQLを実行します。
Entityへのマッピング処理は、sql.RowsのNext(), Scan()関数を使います。

type Item struct { /* 省略 */ }

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	items, err := find(db)
	if err != nil {
		log.Fatalln("Select Error:", err)
		return
	}
	log.Println(items)
}

func open() (*sql.DB, error) { /* 省略 */}

func find(db *sql.DB) ([]Item, error) {
	rows, err := db.Query("SELECT id, name, price, description FROM items")
	if err != nil {
		return nil, err
	}

	// Item Entityへのマッピング処理
	var items []Item
	for rows.Next() {
		item := &Item{}
		if err := rows.Scan(&item.Id, &item.Name, &item.Price, &item.Description); err != nil {
			return nil, err
		}
		items = append(items, *item)
	}

	return items, nil
}

実行後のコンソールログ

2022/11/30 18:07:21 [{001 pen 100 {Color is black. true}}]

3.4. Update

sql.DBのExec()関数を利用して、SQLを実行します。
更新された行数を取得したい場合は、RowsAffected()関数を利用します。

type Item struct { /* 省略 */ }

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	if err := update(db, "001", 200); err != nil {
		log.Fatalln("Upadate Error:", err)
		return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func update(db *sql.DB, id string, price int) error {
	result, err := db.Exec(
		"UPDATE items SET price = :1 WHERE id = :2",
		price, id,
	)
	if err != nil {
		return err
	}

	affectedRowNum, err := result.RowsAffected()
	if err != nil {
		return err
	} else if affectedRowNum != 1 { // id指定によるupdateのため、更新レコード数が1以外はエラー
		return errors.New("number of updated rows is incorrect")
	}
	return err
}

実行後のDB結果

ID  NAME            PRICE DESCRIPTION
--- ---------- ---------- --------------------
001 pen               200 Color is black.

3.5. Delete

sql.DBのExec()関数を利用して、SQLを実行します。

type Item struct { /* 省略 */ }

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	if err := delete(db, "001"); err != nil {
		log.Fatalln("Delete Error:", err)
		return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func delete(db *sql.DB, id string) error {
	_, err := db.Exec("DELETE FROM items WHERE id = :1", id)
	return err
}

実行後のDB結果

ID  NAME            PRICE DESCRIPTION
--- ---------- ---------- --------------------

4. DDL編

4.1. Create Table

sql.DBのExec()関数を利用して、SQLを実行します。

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	if err := createTable(db); err != nil {
		log.Fatalln("Create Table Error:", err)
		return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func createTable(db *sql.DB) error {
	_, err := db.Exec(
		`CREATE TABLE items (
			id VARCHAR2(3) NOT NULL,
			name VARCHAR2(10) NOT NULL,
			price INT NOT NULL,
			description VARCHAR2(20),
			constraint items_pk primary key (id)
		)`,
	)
	return err
}

実行後のDB結果

TABLE_NAME
--------------------------------------------------------------------------------
ITEMS

4.2. Drop Table

sql.DBのExec()関数を利用して、SQLを実行します。

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	if err := dropTable(db); err != nil {
		log.Fatalln("Drop Table Error:", err)
		return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func dropTable(db *sql.DB) error {
	_, err := db.Exec("DROP TABLE items")
	return err
}

実行後のDB結果

TABLE_NAME
--------------------------------------------------------------------------------

5. DCL編

5.1. Rollback

トランザクションの開始はsql.DBBegin()関数を使い、返り値のsql.Txでトランザクション制御を行います。

3, 4章では、sql.DBのQuery()Exec()を利用していましたが、
トランザクション制御する場合は、sql.TxのQuery()Exec()を利用する必要があります。

ロールバックはsql.TxのRollback()関数を使います。

type Item struct { /* 省略 */ }

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	if err := rollback(db); err != nil {
		log.Fatalln("Rollback Error:", err)
		return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func rollback(db *sql.DB) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}

	_, err = tx.Exec("INSERT INTO items (id, name, price) VALUES ('001', 'pen', 100)")
	if err != nil {
		return err
	}

	return tx.Rollback()
}

実行後のDB結果

ID  NAME            PRICE DESCRIPTION
--- ---------- ---------- --------------------

5.2. Commit

トランザクションの開始はsql.DBBegin()関数を使い、コミットはsql.TxのCommit()関数を使います。

type Item struct { /* 省略 */ }

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	if err := commit(db); err != nil {
		log.Fatalln("Commit Error:", err)
		return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func commit(db *sql.DB) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}

	_, err = tx.Exec("INSERT INTO items (id, name, price) VALUES ('001', 'pen', 100)")
	if err != nil {
		return err
	}

	return tx.Commit()
}

実行後のDB結果

ID  NAME            PRICE DESCRIPTION
--- ---------- ---------- --------------------
001 pen               100

6. Timeout編

6.1 Query Timeout

contextのWithTimeout()で指定した時間を経過後に、クエリの実行を中断させることができます。
contextを設定して、クエリを実行するためには、QueryContext() or ExecContext()関数を使います。

サンプルでは、タイムアウト時間を極限まで小さくすることで、タイムアウトが発生させています。

type Item struct { /* 省略 */ }

func main() {
	db, err := open()
	if err != nil {
		log.Fatalln("DB Open Error:", err)
		return
	}
	defer db.Close()

	if err := queryTimeout(db); err != nil {
		log.Fatalln("Query Timeout Error:", err)
        return
	}
}

func open() (*sql.DB, error) { /* 省略 */}

func queryTimeout(db *sql.DB) error {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
	defer cancel()

	_, err := db.QueryContext(ctx, "SELECT id, name, price, description FROM items")

	return err
}

実行後のコンソールログ

2022/11/30 19:19:29 Query Timeout Error: context deadline exceeded

7. 最後に

sijms/go-oraを使って基本的なDB処理を実装してみたところ、簡単なDB処理は問題なく動かせることを確認できました。

sijms/go-oraを実際に使ってみた感想は、OR Mapperが使えないのでどうしても実装量が多くなったり、複雑化したりしてしまうという印象です。
ただ、他のDriverに比べて圧倒的に導入が簡単なので、複雑なDB操作をしないのであればsijms/go-oraを利用したいなと思いました。

また、フェイルオーバー(RAC, Data Guard)への対応方法は未調査のため、今後調べてみたいと思います。

参考文献

22
1
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
22
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?