DBFlute Intro + H2 DatabaseでDB環境構築

# はじめに
※本記事は[以前書いた記事](https://qiita.com/huge-book-storage/items/fb59e3ac5e3fda5c6ead)の焼き直しで、DBをApache Derby → H2DBに変更したものです。
そのため重複は多々ありますが、基本的にself containedな記事を目指し、DRYは目指しません。

データベースに接続するアプリケーションを作成しようとした場合、環境構築面で色々と面倒なことが多いです。
ちょっとO/Rマッパを試してみようとしても、DBサーバをローカルにインストールしたり、テーブルを作成してデータ投入したりといった具合です。
最近はDocker等コンテナ技術を使って楽に環境構築を行えるようにすることも可能な様ですが、教育用等でサンプルアプリケーションを配布して、できるだけ楽に環境構築して早く実行してもらいたいといった場合に少々敷居が高くなってしまいます。

できれば小容量で配布しやすく、即実行できる環境がほしい……そんな悩みを解決してくれそうなのが**H2DB**です。

### [H2DB](https://www.h2database.com/html/main.html)とは
H2DBとは、Java純製の軽量のRDBです。ファイルアクセスorメモリアクセスのみで手軽に実行でき、教育用やサンプルアプリケーション配布用にとても向いています。
また、サーバ機能もあるため、複数アプリから接続することも可能です（例えばWebアプリからDB接続しつつ、コンソールからテーブルの中身を確認する等）。

### [DBFlute](http://dbflute.seasar.org/ja/introduction/index.html#about)とは
RDBの開発支援ツールで、ざっくり次の２つの機能があります。

- O/Rマッパー
- DB管理支援ツール

本記事は２つ目のDB管理支援ツールをメインに扱います。
これを使うとテーブル作成およびデータ投入を１バッチ実行で行えるようになります。
なお、O/Rマッパとしてのサンプルコードも少し書きます。

### [DBFlute Intro](http://dbflute.seasar.org/ja/manual/function/generator/intro/index.html#about)とは
DBFluteは様々な機能を備えており、それらを遂行するための専用エンジンが存在します。
そのため、DBFluteを最初に使う際、エンジンのダウンロードや環境設定等が必要が必要となりますが、そのような環境を簡単に整えるためのツールです。
実行可能jar形式で配布されており、Javaが使える環境であればすぐに動かすことができます（ https://github.com/dbflute/dbflute-intro/releases ）。

### お試しするO/Rマッパ
DBアクセス環境を整えたあと、お試しとして下記３種類のO/Rマッパを使ってみます。

- DBFlute
- MyBatis
- Spring JDBC

### バージョンについて
本記事では下記バージョンを使用します。

- Java 11（AdoptOpenJDK）
- Spring Boot 2.1.7
- DBFlute Intro 0.3.5
- H2DB 1.4.199
- MyBatis 3.5.2 （MyBatis-Spring-Boot-Starter 2.1.0）
- Spring JDBC 5.1.9

# 手順
## 0. Eclipseダウンロード
せっかくなのでコーディング環境、実行環境を整えるところから始めます。
Eclipseである必要性はありませんが、面倒を省くため、ここでは[Pleiades All in One](http://mergedoc.osdn.jp/)に頼ることにします。
http://mergedoc.osdn.jp/ から、WindowsまたはMacのjkd付きJava Full Editionをダウンロードします。

## 1. Spring Initializerからプロジェクト作成
https://start.spring.io/ からSpring Bootプロジェクトを作成します。
その際、Spring Bootの依存に次を追加します。

- H2 Database
- JDBC
- MyBatis

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/2461ae5e-d76a-debc-efde-683f64e303b8.png)

ダウンロードしたファイルを適当な場所に解凍します（eclipseを使う場合はワークスペース下など）。
sample-dbaccess-h2
├─ .gitignore
├─ .mvn
├─ HELP.md
├─ mvnw
├─ mvnw.cmd
├─ pom.xml
└─ src

## 2. DBFlute Introダウンロード、配置
githubからjarファイルを入手します。
https://github.com/dbflute/dbflute-intro/releases

ダウンロードしたファイルを、先ほど解凍したSpringのプロジェクト直下に配置します。
sample-dbaccess-h2
├─ .gitignore
├─ .mvn
├─ **dbflute-intro.jar**
├─ HELP.md
├─ mvnw
├─ mvnw.cmd
├─ pom.xml
└─ src

## 3. DBFlute Introの実行
dbflute-intro.jarファイルを実行します。
後々のために、javaのパスが通っていない場合は通しておきます。

```cmd
set PATH=%PATH%;C:\path\to\eclipse_java\11\bin
java -jar dbflute-intro.jar
```

実行すると、内部でローカルサーバが立ち上がりブラウザで開かれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/9b40316d-a1b6-124e-4e6a-d7cd81f26837.png)

## 4. DBFluteエンジン初期セットアップ
必要な事項を入力して初期セットアップを行います。
入力が完了したら画面したの「Create」ボタンをクリックして実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/712de5c5-abad-2bdd-ea70-ec8347c67d7d.png)

入力の際は以下の点に注意する必要があります。

- Project Name: 任意
- DBMS: H2 Database
- URL: jdbc:h2:file:*../_h2/h2data*
- Schema: PUBLIC　※1
- User: sa　※1
- Password: 任意（空白でOK）

※1 スキーマ「PUBLIC」、ユーザ「sa」を使えば特別な手順等不要で接続できます。ローカルでの検証には十分かと思います。

また、O/RマッパとしてDBFluteをサンプルで使うため、画面右の「O/R Mapper settings」をクリックして入力可能な状態にして、次のものを入力します。

- Language: Java
- DI Container: Spring Framework
- Generation Package: 任意のパッケージ（ここで指定したパッケージ下にDBFluteのソースコードが生成されます）


「Create」ボタンをクリックして実行に成功すると、次の画面が表示されます。!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/f0d8c62a-181c-292a-4999-411db4fb63d3.png)


プロジェクト内にファイルが増えました。
sample-dbaccess
├─ .gitignore
├─ .mvn
├─ dbflute-intro.jar
├─ **dbflute_sample_dbaccess_h2**
├─ **mydbflute**
├─ HELP.md
├─ mvnw
├─ mvnw.cmd
├─ pom.xml
└─ src

- mydbflute: DBFluteのエンジンです。基本的にここを編集することはありません。
- dbflute_*xxx*: プロジェクトごとのDBFluteの設定等が入ったフォルダです。「xxx」には初期セットアップで入力した「Project Name」が入ります。

## 5. DDL作成
先ほど作成されたプロジェクト固有のDBFluteフォルダ「dbflute_sample_dbaccess_h2」内のreplace-schema.sqlにテーブルcreate文等のDDLを入力します。

```sql:dbflute_sample_dbaccess_h2/playsql/replace-schema.sql
create table DEPARTMENT (
	DEPARTMENT_ID int not null,
	DEPARTMENT_NAME varchar(100) not null,
	constraint PK_DEPARTMENT primary key(DEPARTMENT_ID),
	constraint FK_DEPARTMENT_1 FOREIGN KEY (DEPARTMENT_ID) references DEPARTMENT(DEPARTMENT_ID)
);

create table EMPLOYEE (
	EMPLOYEE_ID int not null,
	EMPLOYEE_NAME varchar(100) not null,
	DEPARTMENT_ID int not null,
	constraint PK_EMPLOYEE primary key(EMPLOYEE_ID),
	constraint FK_EMPLOYEE_1 FOREIGN KEY (DEPARTMENT_ID) references DEPARTMENT(DEPARTMENT_ID)
);
```

## 6. 登録用データ作成
今回はtsv形式（タブを区切り文字としたデータ形式）で登録用データを作成します。
データ登録の際に特殊なルールがあり、特にディレクトリ名、ファイル名に注意が必要です。
詳細は「[データ登録(TSV)](http://dbflute.seasar.org/ja/manual/function/generator/task/replaceschema/loadtsv.html)」を参照してください。

```dbflute_sample_dbaccess_h2/playsql/data/ut/tsv/UTF-8/01-department.tsv
DEPARTMENT_ID	DEPARTMENT_NAME
1	部署1
2	部署2
3	部署3
```

```dbflute_sample_dbaccess_h2/playsql/data/ut/tsv/UTF-8/02-employee.tsv
EMPLOYEE_ID	EMPLOYEE_NAME	DEPARTMENT_ID
11	従業員11	1
12	従業員12	1
13	従業員13	1
21	従業員21	2
22	従業員22	2
23	従業員23	2
31	従業員31	3
32	従業員32	3
33	従業員33	3

```

## 7.スキーマ作成＆データ投入
DBFlute Introの画面で、作成したプロジェクトを選択して、Replace Schemaボタンを押してスキーマ作成＆データ投入を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/c46e61ee-cc2c-6d8f-d0cc-3876242abf89.png)

もしくは、manage.batファイルを実行してもOKです。
javaのパスが通っていることに注意してください。

```bat
cd dbflute_sample_dbaccess_h2
manage.bat 0
```

プロジェクト直下にh2用のデータファイルが増えました。
ここで作成されるファイル名（\*.mv.db）は、DBFlute Introの初期セットアップで入力した接続URLで指定しています。
sample-dbaccess-h2
├─ .gitignore
├─ .mvn
├─ **_h2**
　├─ **h2data.mv.db**
├─ dbflute-intro.jar
├─ dbflute_sample_dbaccess_h2
├─ mydbflute
├─ HELP.md
├─ mvnw
├─ mvnw.cmd
├─ pom.xml
└─ src


## 8. ソースコード自動生成
DBFlute用のソースコード自動生成がDBFlute Intro画面からできなかったので、コマンドラインから実行します。
manage.batを、引数「2: regenerate」で実行します（もしくは引数なしで実行して、後から入力することもできます）。
詳細は「[Manageタスク](http://dbflute.seasar.org/ja/manual/function/generator/task/manage/index.html#subtasknumber)」を参照してください。

```bat
cd dbflute_sample_dbaccess
manage.bat 2
```

成功すると、4. DBFluteエンジン初期セットアップで入力したパッケージ下に自動生成コードが出力されます。

## 9. Springの設定追加
Spring上でO/Rマッパを使用するための設定を追加します。

まずpom.xmlを編集します。Javaのバージョンを調整して、DBFluteの依存を追加します。

```xml:pom.xml
<properties>
    <java.version>1.11</java.version>
</properties>
<!-- 中略 -->
<dependencies>
    <!-- 色々な既存の依存関係に追加 -->
    <dependency>
        <groupId>org.dbflute</groupId>
        <artifactId>dbflute-runtime</artifactId>
        <version>1.2.0</version>
    </dependency>
</dependencies>
```

```src/main/resources/application.yml
# 共通のDB接続情報
spring:
  datasource:
    url: jdbc:h2:file:./_h2/h2data
    username: sa
    driverClassName: org.h2.Driver

# MyBatis用設定. lower_case のカラム名を camelCase のプロパティにマッピングする
mybatis:
  configuration:
    mapUnderscoreToCamelCase: true
```

### 注意1. 接続文字列について
h2の接続文字列は`h2:file:ファイルパス`となります。
ここで、ファイルパスは、javaを実行する際の基底ディレクトからの相対パス（eclipse上から実行する場合、プロジェクト直下）、もしくは絶対パスを指定します。

### 注意2. ファイルパスについて
h2のファイルパス設定で、`url: jdbc:h2:file:./_h2/h2data`ではなく`url: jdbc:h2:file:_h2/h2data`とすると実行時に次のようなエラーになります。

```
暗黙的なカレントディレクトリからの相対ファイルパスをデータベースURL("jdbc:h2:file:_h2/h2data")に指定することは許可されていません。代わりに絶対パスか相対パス( ~/name, ./name)あるいは baseDir を指定して下さい.
A file path that is implicitly relative to the current working directory is not allowed in the database URL "jdbc:h2:file:_h2/h2data". Use an absolute path, ~/name, ./name, or the baseDir setting instead. [90011-199]
```

相対パスを用いるのであれば、明示的にカレント`.`から始まるパスで書く必要があるようです。

### 注意3. eclipse上でエラーになる場合
プロジェクトを選択して、「Maven > プロジェクトの更新」を行ってみてください。

## 10. DBFluteお試し実行
次のファイルを追加して、お手軽にコマンドラインでSpringを起動します。

```SampleDbaccessH2Application.java
@SpringBootApplication
public class SampleDbaccessH2DBFluteApplication {

    @Autowired
    EmployeeBhv employeeBhv;

    public static void main(String[] args) {
        try (ConfigurableApplicationContext ctx = SpringApplication.run(SampleDbaccessH2DBFluteApplication.class, args)) {
            SampleDbaccessH2DBFluteApplication app = ctx.getBean(SampleDbaccessH2DBFluteApplication.class);
            app.run(args);
        }
    }

    private void run(String... args) {
        System.out.println("処理開始");
        employeeBhv.selectList(cb -> {
            cb.setupSelect_Department();
            cb.query().setDepartmentId_Equal(2);
        }).forEach(employee -> {
            System.out.println(
                String.format(
                    "employeeName: %s, departmentName: %s",
                    employee.getEmployeeName(),
                    employee.getDepartment().get().getDepartmentName()));
        });
        //アプリの処理
        System.out.println("処理終了");
    }
}
```

## 11. MyBatisお試し実行
```SampleDbaccessH2MyBatisApplication.java
@SpringBootApplication
public class SampleDbaccessH2MyBatisApplication {

    @Autowired
    EmployeeMapper employeeMapper;

    public static void main(String[] args) {
        try (ConfigurableApplicationContext ctx = SpringApplication.run(SampleDbaccessH2MyBatisApplication.class, args)) {
            SampleDbaccessH2MyBatisApplication app = ctx.getBean(SampleDbaccessH2MyBatisApplication.class);
            app.run(args);
        }
    }

    private void run(String... args) {
        System.out.println("処理開始");
        employeeMapper.findEmployeeList(2).forEach(employee -> {
            if (employee != null) {
                System.out.println(
                    String.format(
                        "employeeName: %s, departmentName: %s",
                        employee.getEmployeeName(),
                        employee.getDepartmentName()));
            } else {
                System.out.println("employee is null");
            }
        });
        //アプリの処理
        System.out.println("処理終了");
    }
}
```

```EmployeeMapper.java
@Mapper
public interface EmployeeMapper {
    @Select({ "select emp.EMPLOYEE_NAME as EMPLOYEENAME, dept.DEPARTMENT_NAME",
        "from EMPLOYEE emp",
        "inner join DEPARTMENT dept on dept.DEPARTMENT_ID = emp.DEPARTMENT_ID",
        "where emp.DEPARTMENT_ID = #{departmentId}", })
    List<Employee> findEmployeeList(int departmentId);
}
```

```Employee.java
public class Employee {
    private String employeeName;
    private String departmentName;
    
    // getter, setter
}
```

## 12. Spring JDBCお試し実行

```SampleDbaccessH2JDBCApplication.java
@SpringBootApplication
public class SampleDbaccessH2JDBCApplication {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public static void main(String[] args) {
        try (ConfigurableApplicationContext ctx = SpringApplication.run(SampleDbaccessH2JDBCApplication.class, args)) {
            SampleDbaccessH2JDBCApplication app = ctx.getBean(SampleDbaccessH2JDBCApplication.class);
            app.run(args);
        }
    }

    private void run(String... args) {
        System.out.println("処理開始");
        findEmployeeList(2).forEach(map -> {
            System.out.println(
                String.format(
                    "employeeName: %s, departmentName: %s",
                    map.get("EMPLOYEE_NAME"),
                    map.get("DEPARTMENT_NAME")));
        });
        System.out.println("処理終了");
    }

    private List<Map<String, Object>> findEmployeeList(int departmentId) {
        return jdbcTemplate.queryForList(String.join("", new String[] {
            "select emp.EMPLOYEE_NAME, dept.DEPARTMENT_NAME",
            " from EMPLOYEE emp",
            " inner join DEPARTMENT dept on dept.DEPARTMENT_ID = emp.DEPARTMENT_ID",
            " where emp.DEPARTMENT_ID = ?"
        }), departmentId);
    }
}
```

# おわりに
やはりコピペ作成記事は楽だなと思いました。
ソースコードを書くときはコピペで済ませず、DRYを目指さないとですね…。

## 補足A. DB管理支援ツールとしてのDBFlute
テーブル追加変更がある場合、replace-schema.sqlおよび登録データを修正し、manage.batを実行すれば、既存のテーブル削除＆再生成してくれて、開発環境でマイグレーションが可能です。
また、スキーマ差分比較ツールなど、DBFluteはマイグレーションツールとしても非常に優秀な機能を備えています（参考：「[20160521 大規模映像配信サービスの Java8による全面リニューアルの裏側](https://www.slideshare.net/SuguruAkiho/20160521-jjug-cccunext)」。
O/Rマッパとして導入できなくても、DB管理支援ツールとしての導入をぜひ検討してみてください。

# 補足B：H2 Databaseをサーバ起動して複数同時アクセス可能にする
複数アプリケーションから同時にDBアクセスを行いたい場合、`file:...`による接続では制約上不可能です。
複数同時アクセスは本番環境のみならず、ローカルの開発環境でも起こり得ます。
例えば、Webアプリケーション起動中に、DBFluteなどでスキーマの更新を行いたい場合などです。
これを行うには、サーバ機能を使用する必要があります。

以下、サーバ起動する手順を簡単に記述します。

## H2DBのダウンロード
[本家サイト](https://www.h2database.com/html/main.html)からAll Platformsの方をダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/d8203703-fd61-98e6-65ff-a594f2d0d915.png)

## 必要なファイル移動
ダウンロードしたzipファイルを解凍して、必要なファイルをプロジェクトの方に移動します。
ランタイムだけで良いので、bin配下から次のファイルをh2用のディレクトリ下に持っていきます。
- 起動スクリプト。環境に応じて選択（Windowsだったらbin/h2.bat）
- bin/h2.jar

プロジェクトの構成は次のようになります。

sample-dbaccess-h2
├─ .gitignore
├─.mvn
├─ \_h2
　├─ h2data.mv.db
　├─ h2data.trace.db
　├─ **h2.bat**
　├─ **h2-1.4.199.jar**
├─ dbflute-intro.jar
├─ dbflute_sample_dbaccess_h2
├─ mydbflute
├─ HELP.md
├─ mvnw
├─ mvnw.cmd
├─ pom.xml
└─ src

## 起動スクリプト実行
上記で移動した起動スクリプトと同じディレクトリ下にcdして、起動スクリプトを実行します。
DBFlute Introの実行と同様に、Javaのパスが通っている必要がある点に注意してください。
ブラウザが立ち上がりh2コンソールのログイン画面に遷移します。

## h2コンソールでスキーマ＆データ確認
コンソールにログインする必要ありませんが、ここでは試しにコンソールから投入したデータの確認を行ってみます。
次の情報を入力してコンソールにログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/73b62028-ace8-d9fc-73cd-f212d7f0fbbb.png)

ログインすると、実際にテーブル作成＆データ投入されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299510/426663cd-3f1d-0adb-3a94-f420ddaf9a84.png)


## アプリケーションの設定変更
接続文字列を次のように変更します。
`url: jdbc:h2:tcp://localhost/./h2data`

```src/main/resources/application.yml
spring:
  datasource:
    url: jdbc:h2:tcp://localhost/./h2data
#中略
```

```dbflute_sample_dbaccess_h2/dfprop/databaseInfoMap.dfprop
#中略
    ; url: jdbc:h2:tcp://localhost/./h2data
#中略
```


### 注意1. パスの指定について
既に述べたことと同じ理由で、`url: jdbc:h2:tcp://localhost/h2data`とすることはできません。
`h2data`ではなく明示的に相対パス`./h2data`とする必要があります。

### 注意2. サーバ設定について
検証ができていませんが、サーバ起動する際の設定（ポート等）を設定することができるようです。
http://www.h2database.com/html/tutorial.html#console_settings

### 注意3. 初回のスキーマ構築について
ファイル（上の例だとh2data.mv.db）が出来上がっていない初回アクセス時にH2をサーバ起動して、DBFluteからReplace Schemaを行うとエラーになってしまいました。

```
Database "/path/to/h2/h2data" not found, and IFEXISTS=true, so we cant auto-create it [90146-197]
```
接続オプションに`IFEXISTS=true`を付けると自動作成できない、とエラーメッセージに出ていたので、`IFEXISTS=false`にしてみましたがダメでした。

とりあえず、初回は次の方法で回避しています。

- `tcp:`でなく`file:`でアクセスする。
- H2マネジメントコンソールから初期化する