Eclipse + Java + Maven の環境で Doma を動かす

## はじめに

この記事では、データベースアクセスフレームワークである [Doma](https://github.com/domaframework/doma) を利用する際の開発環境構築についてノウハウを紹介します。
簡単なサンプルアプリケーションを作りながら解説します。

## 前提

### バージョンなど

開発マシンは macOS を想定しますが、Windows と異なるところはないと思います。

IDE、ツール、ライブラリ等のバージョンは以下のとおりです。

- Eclipse IDE for Java Developers 2024-03 (4.31.0) 
- Java 17.0.10（Eclipse のデフォルト）
- Maven 3.9.6（Eclipse のデフォルト） 
- Doma 2.60.0 

### Eclipse の仕組みを踏まえた戦略

Eclipse は以下のようなさまざまな設定ファイルを参照して動きます。

- .classpath : どのライブラリのどのバージョンを使うのかといったクラスパス情報
- .factorypath : アノテーションプロセッサーのライブラリのパス情報
- .project : プロジェクト名などのメタ情報
- .settings : Eclipseの画面からの設定項目など複数ファイルを管理するフォルダ

これらのファイルは Eclipse の 画面上で設定を行うことで出力できますが、その場合の問題点はビルドファイルと Eclipse の設定ファイルで二重管理が必要となってしまうことです。 二重管理を避けるために、ビルドファイルを一次情報としてビルドファイルから Eclipse の設定ファイルを自動生成するのが基本的な戦略となります。

## Eclipse から Maven Project の作成

Eclipse 上部のメニューから File > New > Maven Project を選択しダイアログを開きます。
Create a simple project にチェックを入れ、 Next ボタンを押します。
Group Id と Artifact Id のどちらにも example を入れ、Finish ボタンを押します。

そうすると、example プロジェクトが作成されます。

## Annotation Processing の有効化

example プロジェクトを右クリックして Properties > Maven > Annotaion Processing を選択し、 Enable Project specific settings にチェックを入れ、 Automatically configure JDT APT にもチェックを入れます。

![Properties_for_example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41471/2b93d083-7d19-6d92-ca3e-c3ee855aca40.png)


:::note warn
この設定を有効にしないと、pom.xml から .factorypath など Eclipseのアノテーションプロセッシングに必要なファイルが生成されません。必ず有効にしましょう。
:::


## 生成された pom.xml の確認

 example プロジェクトの pom.xml を確認しましょう。

```xml:pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</project>

```

## pom.xml の修正

オリジナルの pom.xml を以下の内容で置換します。

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>example</groupId>
    <artifactId>example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  
    <properties>
        <doma.version>2.60.0</doma.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.seasar.doma</groupId>
            <artifactId>doma-core</artifactId>
            <version>${doma.version}</version>
        </dependency>
        <dependency>
            <groupId>org.seasar.doma</groupId>
            <artifactId>doma-slf4j</artifactId>
            <version>${doma.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.2.224</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source> <!-- depending on your project -->
                    <target>17</target> <!-- depending on your project -->
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.seasar.doma</groupId>
                            <artifactId>doma-processor</artifactId>
                            <version>${doma.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <compilerArgs>
                        <!-- if you are using a Maven project in Eclipse, this argument is required -->
                        <arg>-Adoma.resources.dir=${project.basedir}/src/main/resources</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
```

ポイントは、 compilerArgs にリソースフォルダを指定することです。これは、注釈処理時にSQLファイルを参照するために必要です。

## Doma を利用するコードの追加

example プロジェクトの src/main/java フォルダの example パッケージに次のクラスを追加します。

```java:DbConfig.java
package example;

import java.util.Objects;

import javax.sql.DataSource;

import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.JdbcLogger;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.H2Dialect;
import org.seasar.doma.jdbc.tx.LocalTransactionDataSource;
import org.seasar.doma.jdbc.tx.LocalTransactionManager;
import org.seasar.doma.jdbc.tx.TransactionManager;
import org.seasar.doma.slf4j.Slf4jJdbcLogger;

public class DbConfig implements Config {

    private final Dialect dialect;
    private final DataSource dataSource;
    private final JdbcLogger jdbcLogger;
    private final TransactionManager transactionManager;

    public DbConfig(Dialect dialect, DataSource dataSource, JdbcLogger jdbcLogger,
            TransactionManager transactionManager) {
        this.dialect = Objects.requireNonNull(dialect);
        this.dataSource = Objects.requireNonNull(dataSource);
        this.jdbcLogger = Objects.requireNonNull(jdbcLogger);
        this.transactionManager = Objects.requireNonNull(transactionManager);
    }

    @Override
    public JdbcLogger getJdbcLogger() {
        return jdbcLogger;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    public static DbConfig of() {
        var dialect = new H2Dialect();
        var dataSource = new LocalTransactionDataSource("jdbc:h2:mem:tutorial;DB_CLOSE_DELAY=-1", "sa", null);
        var jdbcLogger = new Slf4jJdbcLogger();
        var transactionManager = new LocalTransactionManager(dataSource, jdbcLogger);
        return new DbConfig(dialect, dataSource, jdbcLogger, transactionManager);
    }
}
```

```java:Person.java
package example;

import org.seasar.doma.Entity;
import org.seasar.doma.Id;

@Entity
public class Person {

    @Id
    Integer id;

    String name;
}
```

```java:PersonDao.java
package example;

import java.util.List;

import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Script;
import org.seasar.doma.Select;
import org.seasar.doma.Sql;

@Dao
public interface PersonDao {

    @Script
    @Sql("""
            create table if not exists person(
              id integer not null primary key,
              name varchar(20)
            )
            """)
    void createTable();

    @Select
    List<Person> selectAll();

    @Insert
    int insert(Person person);
}
```

example プロジェクトの src/main/resources/META-INF/example/PersonDao フォルダに次の SQL ファイルを追加します。

```sql:selectAll.sql
select /*%expand*/* from person
```

example プロジェクトの src/test/java フォルダの example パッケージに次のクラスを追加します。

```java:PersonDaoTest
package example;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class PersonDaoTest {

    // データベースの設定
    DbConfig dbConfig = DbConfig.of();
    // DAO
    PersonDao dao = new PersonDaoImpl(dbConfig);

    @Test
    public void test() {
        // トランザクション内で実行
        dbConfig.getTransactionManager().required(() -> {
            dao.createTable(); // テーブルの作成

            var person1 = new Person();
            person1.id = 1;
            person1.name = "abc";
            dao.insert(person1); // 追加

            var person2 = new Person();
            person2.id = 2;
            person2.name = "def";
            dao.insert(person2); // 追加

            var people = dao.selectAll(); // 全件取得
            assertEquals(2, people.size());

            for (var p : people) {
                System.out.printf(p.name);
            }
        });

    }
}
```

## 動作確認（Doma 関連の設定が正しいか？）

追加した Doma のコードのビルドや実行を確認します。

### クリーンビルドの実行

Eclipse の上部メニューから Project > Clean を実行して問題が発生しないことを確認します。

### JUnit の実行

example プロジェクトを右クリックして Run As > JUnit Test を選択します。
JUnit ビューから PersonDaoTest　が実行されていること、グリーンのバーが表示されていることを確認します。

### アノテーションプロセッサーによる検証

PersonDao.java の `@Insert` をコメントアウトし、ファイルを保存します。
エディタ上に下記のエラーメッセージが表示されたらアノテーションプロセッサーが適切に動いていると言えます。

```
[DOMA4005] The query annotation such as @Select and @Update is required.
```

確認を終えたらコメントアウトを元に戻してください。

:::note info
ファイル保存時にアノテーションプロセッサーを起動するには、 Eclipse 上部の Project メニューで Build Automatically が有効になっていなければいけません（デフォルトで有効です）。無効にしている場合は、代わりにクリーンビルドを実行してください。
:::

## Q & A
### Maven > Update Project をいつ実行すればいいのですか？

明示的にこのコマンドを実行しなくてもデフォルトで pom.xml の変更が自動的にプロジェクトに反映されるようですが、必要に応じて、プロジェクトや pom.xml を右クリックして Maven > Update Project を実行してください。

### アノテーションプロセッサーのオプションを指定するにはどうすればいいですか？

次のような記述が必要です。[Domaのドキュメント](https://doma.readthedocs.io/ja/latest/annotation-processing/#setting-options-in-maven)も参照ください。

```xml: pom.xml
<compilerArgs>
    <arg>-Adoma.resources.dir=${project.basedir}/src/main/resources</arg>
    <arg>-Adoma.dao.subpackage=impl</arg>
    <arg>-Adoma.dao.suffix=Impl</arg>
</compilerArgs>
```

### Eclipse の設定ファイルを確認するにはどうするのが良いですか？

Eclipse の Package Explorer の右上メニューから Filters... を選択し、 .*resources のチェックを外せば Package Explorer 上で表示できます。 

Visual Studio Code などで表示するのはオススメしません。Visual Studio Code に Java の拡張機能が入っていると勝手に Eclipse の設定を書き換えたりすることがあるので注意が必要です。

## おわりに

もっと良い方法をご存知の方は共有をよろしくお願いします。

Maven ではなく Gradle を利用する場合は、[Eclipse + Java + Gradle の環境で Doma を動かす](https://qiita.com/nakamura-to/items/9e05fe00be9d4d629fdc) を参照ください。