Oracleを使うGoアプリのコンテナ化メモ（準備編）

# はじめに
私のこれまで基本的にJavaしかやって来ませんでした。
また利用するDBもPostgreSQLがメインだったのでOracleDBとは幸いなことにあまり関わってこなかったのですが、仕事関係で色々ありGo言語+OracleDBを使うことになりました。
。
いざ始めてみるとライブラリの設定やアプリのDockerイメージビルドでかなり苦労しながらもGitHubやStackoverflowと睨めっこが続きました。

というわけでこの苦労を忘れてしまわないように、メモとして残そうと思い立ち今に至ります。

# 環境
私の実行環境は以下の通りです。

| 環境/ツール類 | バージョン |
|:-------------|:----------|
| OS           | Windows10 |
| Ubuntu(WSL)  | 20.04.4   |
| Golang       | 18.5      |
| Oracle       | 19c(19.3.0) |
| Docker       | 20.10.17 |
| Docker-Compose | v2.6.1  |

# 1. Oracle19cの準備
まずはじめにOracle19cを用意しないと何も始まりません。
普通にOracleをインストールすればいいのですが、自分の環境をOracleで汚したくなかったのでコンテナで起動することにしました。
調べると結構簡単に自分でビルドしてコンテナ化できるみたいなのでその方法でOracleDBの起動を行います。

下記参考リンクの手順通りに(バージョンを変えて)進めるだけでOKですが、一応私の実施手順も以降に記載します。

https://qiita.com/gorilla0513/items/f22e8cce4e08da031abe

## 1.1 Oracle公式リポジトリのクローン
[Githubの公式リポジトリ](https://github.com/oracle/docker-images/tree/main/OracleDatabase/SingleInstance)からOracleDBのビルド用資材をクローンします。

```shell
$ git clone https://github.com/oracle/docker-images.git
$ cd docker-images
```

## 1.2 Oracle Database 19cのダウンロードとイメージのビルド
[Oracle Database Software Downloads](https://www.oracle.com/database/technologies/oracle-database-software-downloads.html)からOracle19c本体資材をダウンロードします。
Oracle19cの場合、Dockerfileのベースイメージが[oraclelinux](https://github.com/oracle/docker-images/blob/main/OracleDatabase/SingleInstance/dockerfiles/19.3.0/Dockerfile#L23)なのでLinux x86-64のzipを選択します。。。

が！！！！ここで面倒なことが発生します。

それは**Oracleアカウントが必要になる**ということです。
Oracleアカウントを持っていない人は「プロファイルの作成」を行う必要があるのですが、さらに面倒なのが**会社名や会社住所が必須入力項目となっている点**です。
個人の検証用で利用したいのに会社名必須とはどうすれば？ということでググってみると下記の記事がヒットしました。

https://aireblog.com/create-oracle-profile/

どうやら個人の場合は↓の通り設定すればOKとのことです。

* 部署・役職名：「個人」
* 勤務先電話番号：個人携帯の電話番号
* 会社名：個人のフルネーム

これらの面倒なアカウント作成が完了するとようやく、目当てのzipファイルダウンロードが可能となります。

zipファイルのダウンロードできたら**ファイルを解凍せずに[docker-images/OracleDatabase/SingleInstance/dockerfiles/19.3.0/](https://github.com/oracle/docker-images/tree/main/OracleDatabase/SingleInstance/dockerfiles/19.3.0)直下に格納します**。

その後、下記shellスクリプトを実行し、Dockerイメージのビルドを行います。

```shell
docker-images$ ./OracleDatabase/SingleInstance/buildDockerImage.sh -v 19.3.0 -e -i
```
ビルドには結構時間がかかるのでビルド完了まで時間をつぶします。

:::note info
**-vオプションについて**
`-v`で指定するバージョンは`11.2.0.2, 12.1.0.2, 12.2.0.1, 18.3.0, 18.4.0, 19.3.0, 21.3.0`から選択可能です。
今回は利用するバージョンは19cなので`19.3.0`を指定します。
詳細はGithubの[README](https://github.com/oracle/docker-images/blob/master/OracleDatabase/SingleInstance/README.md)を参照してください。
:::

## 1.3 Oracle19cコンテナの起動とPDBへの接続確認
Dockerイメージのビルドが完了したらコンテナを起動します。
コンテナの起動には`docker run`コマンドを利用してもよいのですが、何度も同じ環境を起動できるように下記内容の`docker-compose`ファイルを作成します。

```shell
$ mkdir oracledb
$ chmod 777 ./oracledb
```
```yaml:oracle19c-docker-compose.yaml
version: "3.9"
services:
  db:
    image: oracle/database:19.3.0-ee
    container_name: oracle19c
    volumes:
      - ./oracledb:/opt/oracle/oradata
    ports:
      - 1521:1521
      - 5500:5500
    environment:
      - ORACLE_PWD=oracle
      - ORACLE_SID=GODB
      - ORACLE_PDB=GOPDB
```

その後、バックグラウンドオプションの`-d`を付けて`docker-compose`コマンドでコンテナを起動します。
起動には環境によりますが数十分近くかかるので気長に待ちます。

```shell
$ docker-compose -f oracle19c-docker-compose.yaml up -d
```

:::note info
**バックグラウンド実行について**
バックグラウンド実行しないとずーっとOracleの稼働ログが出力され続けるので-dでバックグラウンド実行させることを推奨します。

ログは下記コマンドで確認し、「DATABASE IS READY TO USE!」と出力されればOKです。
```shell
$ docker logs -f oracle19c
・・・細かなログは省略
#########################
DATABASE IS READY TO USE!
#########################
```
:::

## 1.4 接続用ユーザの作成
単純にコンテナ起動させた状態だと管理者系のユーザ(SYS, SYSTEM, PDBADMIN)しかいません。
なのでアプリでDB操作を行うユーザの作成が必要となります。
まずは起動したコンテナにログインして`sqlplus`でOracleDBに入ります。

```shell
$ docker exec -it oracle19c bash
$ sqlplus SYSTEM/oracle@GODB
SQL*Plus: Release 19.0.0.0.0 - Production on Mon Sep 12 14:20:40 2022
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL>
```
後は下記リンク先の手順に沿ってPDBユーザを作成します。

https://qiita.com/kawakawaryuryu/items/ae51bee866a57f178fe5

まずはSYSTEMユーザにログインしてPDBADMINにユーザ作成権限を付与する。

```sql
SQL> alter session set container = GOPDB;
Session altered.

SQL> show con_name;
CON_NAME
------------------------------
GOPDB

SQL> grant grant any privilege to PDBADMIN;
Grant succeeded.

SQL> grant grant any role to PDBADMIN;
Grant succeeded.

SQL> exit;
```

権限が付与できたのでPDBADMINにログインしてユーザを作成します。

```shell
$ sqlplus pdbadmin/oracle@GOPDB
SQL*Plus: Release 19.0.0.0.0 - Production on Mon Sep 12 14:26:09 2022
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL>
```

```sql
SQL> grant create user to PDB_DBA;
Grant succeeded.

SQL> create user gouser identified by pwd default tablespace users temporary tablespace temp;
User created.

SQL> grant unlimited tablespace to gouser;
Grant succeeded.

SQL> grant connect, resource to gouser;
Grant succeeded.
```

最後に作成したユーザでPDBに接続できればOK。
私の環境にはA5M2というツールが入っているのでそれを使います。

| 設定項目 | 設定値 |
|:-----------|:-------|
| ホスト名         | loalhost |
| ポート番号       | 1521 |
| サービス名(PDB名)| GOPDB |
| ユーザID         | gouser |
| パスワード       | pwd |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/fdbd5c18-7c8d-5f89-9709-6b8e88155c02.png)

接続確認ができればとりあえずOracleDBの準備はOKです。


# 2. 開発環境の構築
次にGoの開発環境を構築します。
Windows環境を利用しているのですが、Windows環境だと`pkg-config`のインストールなどいろいろ面倒なのでWSLのUbuntu環境をGoの実行環境にしたいと思います。
また、今回はGoのOracle接続ライブラリとして[mattn/go-oci8](https://github.com/mattn/go-oci8/blob/master/README.md)を利用します。

参考にしたのはこちら

https://qiita.com/qt-luigi/items/fbbe6792a77b493a58f9


## 2.1 各種インストール
まずは必要なライブラリとGo1.18をインストールします。

```shell
# その他依存ライブラリのインストール
$ sudo apt update && apt upgrade -y
$ sudo apt-get install curl unzip pkg-config git 
# Go 1.18のインストール
$ sudo add-apt-repository ppa:longsleep/golang-backports
$ sudo apt install golang-1.18
```
インストール後各種環境変数の設定を`.bash_profile`に追記します。

```shell:.bash_profile
export PKG_CONFIG_PATH=/usr/lib/pkgconfig/
export GOPATH=~/go
export GOROOT=/usr/lib/go-1.18
export PATH=$GOROOT/bin:$PATH
```

## 2.2 Oracle Instant Clientのダウンロード
[Oracle Instant Client Downloads](https://www.oracle.com/database/technologies/instant-client/downloads.html)から自分の環境にあったクライアントをダウンロードします。
こちらのファイルのダウンロードにOracleアカウントは不要となります。

「Linux x86-64 (64-bit)」の下記をダウンロードします。

* Basic Package
* SDK Package

バージョンは執筆時点最新の「Version 21.7.0.0.0」を選択し`/usr/local`ディレクトリにダウンロードしたファイルを解凍します。

```shell
$ curl -s -o instantclient-basic-linux.x64-21.7.0.0.0dbru.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-basic-linux.x64-21.7.0.0.0dbru.zip
$ curl -s -o instantclient-sdk-linux.x64-21.7.0.0.0dbru.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-sdk-linux.x64-21.7.0.0.0dbru.zip
 
$ ll | grep instantclient
instantclient-basic-linux.x64-21.7.0.0.0dbru.zip
instantclient-sdk-linux.x64-21.7.0.0.0dbru.zip

$ sudo unzip instantclient-basic-linux.x64-21.7.0.0.0dbru.zip -d /usr/local
$ sudo unzip instantclient-sdk-linux.x64-21.7.0.0.0dbru.zip -d /usr/local
```

`/usr/local`にファイルを配置できたら、環境変数に`LD_LIBRARY_PATH`を設定するため`.bash_profile`に設定を追記します。

```shell:.bash_profile
export LD_LIBRARY_PATH=/usr/local/instantclient_21_7
```

最後に`.bash_profile`の再読み込みを実行します。

```shell
$ source ~/.bash_profile
```

## 2.3 oci8.pcファイルの作成
下記ファイルを`/usr/lib/pkgconfig/`に格納する。

```pc:oci8.pc
prefix=/usr/local/instantclient_21_7
libdir=${prefix}
includedir=${prefix}/sdk/include/

glib_genmarshal=glib-genmarshal
gobject_query=gobject-query
glib_mkenums=glib-mkenums

Name: oci8
Description: oci8 library
Libs: -L${libdir} -lclntsh
Cflags: -I${includedir}
Version: 12.2
```

とりあえずこれでoci8利用に向けた準備作業はOK。


# 3. Goアプリの実装
次に肝心のGoアプリを実装します。

## 3.1 アプリの実装
正直実装は↓の方コードを〇パクリです。
確認したいのはDockerイメージビルド時のociの扱いについてなので細かいアプリの中はどうでもいいです。

https://qiita.com/naoki_koreeda/items/8cd2ce4e70a82c48ec07

```go:main.go
package main

import (
	"database/sql"
	"log"

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

func main() {

	connectStr := "gouser/pwd@localhost:1521/GOPDB"
	db, err := sql.Open("oci8", connectStr)
	if err != nil {
		log.Printf("DbOpen Error: %s", err)
		return
	}
	defer db.Close()

	// 以下のようにクエリ投げる
	rows, err := db.Query("SELECT USER_ID, USER_NAME FROM SAMPLE_USER")
	if err != nil {
		log.Printf("Query Error: %s", err)
		return
	}
	defer rows.Close()

	// 以下のようにデータを取得する
	for rows.Next() {
		var id int
		var name string
		rows.Scan(&id, &name)
		log.Printf("ID: %d  Name: %s", id, name)
	}
}
```


## 3.2 テスト用テーブルとデータの作成
動作確認用のテーブルとして`sample_user`テーブルとテストデータをOracleに登録します。
SQLの流し方はA5M2を使うも、コンテナに入ってからsqlplusで流すも好きな方法を利用してください。

```sql
CREATE TABLE sample_user (
  user_id    VARCHAR2(10) NOT NULL,
  user_name  VARCHAR2(30) NOT NULL,
  user_age   NUMBER(3, 0) NOT NULL,
  CONSTRAINT pk1 PRIMARY KEY(user_id)
);
INSERT INTO sample_user (user_id, user_name, user_age) VALUES ('00001', 'testuser01', 20);
INSERT INTO sample_user (user_id, user_name, user_age) VALUES ('00002', 'testuser02', 25);
INSERT INTO sample_user (user_id, user_name, user_age) VALUES ('00003', 'testuser03', 40);
```

## 3.3 動作確認
最後にGoアプリの動作確認です。
とりあえず、プロジェクト(`main.go`しかないですが)をビルドして生成されたバイナリを実行します。

```shell
$ go mod download
$ go build -o go-sample
$ ./go-sample
2022/09/13 23:49:43 Version:default/Revision:dev
2022/09/13 23:49:43 ID: 1  Name: testuser01
2022/09/13 23:49:43 ID: 2  Name: testuser02
2022/09/13 23:49:43 ID: 3  Name: testuser03
```

無事Oracleからユーザ情報を取得してログに出力することができました。
とりあえず動くところまでは来たので今回はここまでとします。


https://qiita.com/yuuman/items/9f988a9cf4b6dcf4a969

に続く。