はじめに
この記事では、データベースアクセスフレームワークである Doma を利用する際の開発環境構築についてノウハウを紹介します。
簡単なサンプルアプリケーションを作りながら解説します。
前提
バージョンなど
開発マシンは macOS を想定しますが、Windows と異なるところはないと思います。
IDE、ツール、ライブラリ等のバージョンは以下のとおりです。
- Eclipse IDE for Java Developers 2024-03 (4.31.0)
- Java 17.0.10(Eclipse のデフォルト)
- Gradle 8.1.1(Eclipse で Gradle Project 作成時に利用されるバージョン)
- Doma 2.58.0
Eclipse の仕組みを踏まえた戦略
Eclipse は以下のようなさまざまな設定ファイルを参照して動きます。
- .classpath : どのライブラリのどのバージョンを使うのかといったクラスパス情報
- .factorypath : アノテーションプロセッサーのライブラリのパス情報
- .project : プロジェクト名などのメタ情報
- .settings : Eclipseの画面からの設定項目など複数ファイルを管理するフォルダ
これらのファイルは Eclipse の 画面上で設定を行うことで出力できますが、その場合の問題点はビルドファイルと Eclipse の設定ファイルで二重管理が必要となってしまうことです。 二重管理を避けるために、ビルドファイルを一次情報としてビルドファイルから Eclipse の設定ファイルを自動生成するのが基本的な戦略となります。
Eclipse から Gradle Project の作成
Eclipse 上部のメニューから File > New > Project... > Gradle > Gradle Project を選択しダイアログを進めます。プロジェクト名には example を入力して Finish ボタンを押します。
そうすると、example と lib の2つのプロジェクトが作成されます。
生成された build.gradle の確認
lib プロジェクトの build.gradle を確認しましょう。
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java library project to get you started.
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/8.1.1/userguide/building_java_projects.html
*/
plugins {
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:31.1-jre'
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
上部のコメントにあるように、これはサンプル用のビルドファイルです。
まずはこの状態で動作確認したいところですが、私の環境では Eclipse 上から JUnit が動きませんでした。Eclipse の JUnit と build.gradle に記載の JUnit のバージョンに互換性がないからだと思われます。
build.gradle の修正
オリジナルの build.gradle を以下の内容で置換します。
plugins {
id 'java-library'
// Eclipse のアノテーションプロセッシング関連の設定ファイルを生成するプラグイン
id 'com.diffplug.eclipse.apt' version '3.44.0'
// Doma を使ったビルドをサポートするプラグイン
id 'org.domaframework.doma.compile' version '2.0.0'
}
repositories {
mavenCentral()
}
dependencies {
// 最新の JUnit を利用。
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
// Doma の核となるライブラリ。ビルド時も実行時も必要。
implementation 'org.seasar.doma:doma-core:2.58.0'
// Doma のログをSLF4J経由で出力するライブラリ。
implementation("org.seasar.doma:doma-slf4j:2.58.0")
// SLF4J の実装ライブラリ。
runtimeOnly("ch.qos.logback:logback-classic:1.2.11")
// Doma のアノテーションプロセッサー。ビルド時のみ必要。
annotationProcessor 'org.seasar.doma:doma-processor:2.58.0'
// 本サンプルで利用する H2 Database Engine。データベースでありJDBCドライバである。
runtimeOnly 'com.h2database:h2:2.2.224'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.named('test') {
useJUnitPlatform()
}
// Eclipse 関連の設定
eclipse {
// .classpath ファイルに関する設定
classpath {
file {
whenMerged { classpath ->
// アノテーションプロセッサーで生成したソースコードを保存するフォルダ
def folder = new org.gradle.plugins.ide.eclipse.model.SourceFolder(".apt_generated", "bin/main")
// クラスパスに追加
classpath.entries.add(folder)
// 存在しなければ作成
def dir = file(folder.path)
if (!dir.exists()) {
dir.mkdir()
}
}
}
}
// .project ファイルに関する設定
project {
buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
natures 'org.eclipse.buildship.core.gradleprojectnature'
}
// Eclipse から "Refresh Gradle Project" を呼び出した時に実行されるタスク
synchronizationTasks 'cleanEclipse', 'eclipse'
}
ポイントは以下のとおりです。
- plugins ブロックに Eclipse や Doma に関するプラグインを指定します。
- dependencies ブロックに Doma 関連のライブラリを記載します。JUnit のライブラリも変更します。
- eclipse ブロックに Eclipse 関連の設定ファイルを生成するための記述をします。
特にコメントを入れていないところはオリジナルの build.gradle にも記載があったものです。詳細はオリジナルの方のコメントを参照ください。
動作確認(build.gradle の記述に問題ないか?)
では、置換した build.gradle が正しく動くか確認しましょう。
Refresh Gradle Project の実行
example か lib プロジェクトを右クリックして Gradle > Refresh Gradle Project を実行してください。Refresh Gradle Project を行うと build.gradle に記載した synchronizationTasks に指定したタスクが動きます。synchronizationTasks には 以下の2つの Gradle タスクを設定していました。
- cleanEclipse: Eclipse の設定ファイルを削除するタスク
- eclipse: Eclipse 設定ファイルを生成するタスク
Refresh Gradle Project の実行結果は Eclipse のコンソール上に以下のように出力されます。BUILD SUCCESSFUL の文字列が出力されていれば成功です。
> Task :lib:cleanEclipseClasspath
> Task :lib:cleanEclipseFactorypath
> Task :lib:cleanEclipseJdt
> Task :lib:cleanEclipseJdtApt
> Task :lib:cleanEclipseProject
> Task :lib:cleanEclipse UP-TO-DATE
> Task :lib:eclipseClasspath
> Task :lib:eclipseFactorypath
> Task :lib:eclipseJdt
> Task :lib:eclipseJdtApt
> Task :lib:eclipseProject
> Task :lib:eclipse
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
See https://docs.gradle.org/8.1.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 712ms
11 actionable tasks: 10 executed, 1 up-to-date
コンソールへの出力を確認するには、コンソールビューの右上の Display Selected Console アイコンから [Gradle Operations] を選んでください。
JUnit の実行
次に lib プロジェクトを右クリックして Run As > JUnit Test を選択します。
JUnit ビューにグリーンのバーが出力されれば成功です。
Doma を利用するコードの追加
lib プロジェクトの src/main/java フォルダの example パッケージに次のクラスを追加します。
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);
}
}
package example;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
@Entity
public class Person {
@Id
Integer id;
String name;
}
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);
}
lib プロジェクトの src/main/resources/META-INF/example/PersonDao フォルダに次の SQL ファイルを追加します。
select /*%expand*/* from person
lib プロジェクトの src/test/java フォルダの example パッケージに次のクラスを追加します。
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 の実行
lib プロジェクトを右クリックして Run As > JUnit Test を選択します。
JUnit ビューから PersonDaoTest が実行されていること、グリーンのバーが表示されていることを確認します。
アノテーションプロセッサーによる検証
PersonDao.java の @Insert
をコメントアウトし、ファイルを保存します。
エディタ上に下記のエラーメッセージが表示されたらアノテーションプロセッサーが適切に動いていると言えます。
[DOMA4005] The query annotation such as @Select and @Update is required.
確認を終えたらコメントアウトを元に戻してください。
ファイル保存時にアノテーションプロセッサーを起動するには、 Eclipse 上部の Project メニューで Build Automatically が有効になっていなければいけません(デフォルトで有効です)。無効にしている場合は、代わりにクリーンビルドを実行してください。
Q & A
Refresh Gradle Project をいつ実行すればいいのですか?
build.gradle を変更したら実行しましょう。例えば、依存ライブラリを新しく追加したときやライブラリのバージョン番号を変更したときなどです。
ビルドファイルを Groovy ではなく Kotlin DSL で記述する場合の注意点はありますか?
Eclipse や Doma に特化したような注意点は特にないと思います。
Kotlin DSL を使った例はこちらを参照ください。
アノテーションプロセッサーのオプションを指定するにはどうすればいいですか?
次のような記述が必要です。Domaのドキュメントも参照ください。
compileJava {
aptOptions {
processorArgs = [
'doma.debug' : 'true'
]
}
}
アノテーションプロセッサーのオプションは、 Refresh Gradle Project を実施することで Eclipse の設定ファイル(.settings/org.eclipse.jdt.apt.core.prefs)に反映されますが、この設定ファイルは直ちに Eclipse にロードされません。Eclipse 上で有効にするには以下のいずれかの方法を実施してください(もっと良い方法があるかもしれません)。
- Eclipse を再起動する
- .settings/org.eclipse.jdt.apt.core.prefs を Eclipse 上で開き修正して保存する(コメント行に空白を1文字追加するなど)
バグレポートを作ってみました。
https://github.com/eclipse/buildship/issues/1305
Eclipse の設定ファイルを確認するにはどうするのが良いですか?
Eclipse の Package Explorer の右上メニューから Filters... を選択し、 .*resources のチェックを外せば Package Explorer 上で表示できます。
Visual Studio Code などで表示するのはオススメしません。Visual Studio Code に Java の拡張機能が入っていると勝手に Eclipse の設定を書き換えたりすることがあるので注意が必要です。
おわりに
もっと良い方法をご存知の方は共有をよろしくお願いします。
Gradle ではなく Maven を利用する場合は、Eclipse + Java + Maven の環境で Doma を動かす を参照ください。