DomaはSQLを開発者が書き、注釈処理がコンパイル時にそのSQLを検証するJava向けのデータベースアクセスフレームワークです。「Doma2」はその2系を指します。ただし2026年10月時点で2系は2.62.1(2024年8月29日)を最後に更新が止まり、現行はJava 17以上を前提にしたDoma 3(最新3.14.1)です。この記事では、Doma 3.14.1・JDK 25・H2とMySQL 8.4.8で実際にコードを動かした結果をもとに、仕組み・2系と3系の違い・Spring Boot連携・一括登録の性能を整理します。
まとめ
Domaの強みは、SQLファイルの所在・構文・バインド変数とメソッド引数の対応がビルドの時点で検証されることです。一方、SELECT結果の列とエンティティの対応は実行時の検査で、コンパイルは通ります。「コンパイルが通ればすべて安全」ではない点は押さえておいてください。
2系を使い続けるかどうかは、Spring BootとJavaの版で決まります。Doma 3対応のdoma-spring-boot-starter 3.0.0はSpring Boot 4.x以上が要件で、Doma 2向けのスターターは1.8.0で止まっています。Spring Boot 3.5のOSSサポートは2026年6月30日に終了しているため、公式スターターでSpring Bootを上げ続ける限り、Doma 3への移行は避けられません。Spring Bootを使わない場合も、公式READMEの互換表でDoma 2が対応するのはJava 22までです。SQLテンプレートの書式は変わらないため、移行の中心はJava 17への引き上げと、削除されたAPIの置き換えです。
見落とされがちなのが一括登録です。@BatchInsertはbatchSizeを指定しないと1行ずつデータベースへ送ります。MySQLで1万件を登録した実測では、既定のままだと約6.1秒、batchSize = 1000とConnector/JのrewriteBatchedStatements=trueを併用すると約0.7秒でした。
Doma2とは:SQLを書いてコンパイル時に検証するデータベースアクセスフレームワーク
公式リポジトリはDomaを「DAO oriented database mapping framework for Java」と説明しています。ライセンスはApache License 2.0で、doma-coreは他のライブラリに依存しません。日本語版公式ドキュメントの2系のトップは「Doma 2へようこそ」で、「Doma 2はJava 8+向けのデータベースアクセスフレームワーク」と書かれています。DomaとDoma2は同じ製品で、2は版の系列を表します。
コンパイル時に検証されること・実行時まで分からないこと
Domaの中核はdoma-processorです。javacの注釈処理として動き、@Daoを付けたインターフェースからEmployeeDaoImplのような実装クラスを生成します。生成の過程でSQLファイルが検査されるため、次の2つはビルドが失敗します。Doma 3.14.1で実際に出たメッセージです。
// SQLファイル内の変数名をメソッド引数と食い違わせた場合
[DOMA4092] Failed to verify the SQL template "META-INF/exp/EmployeeDao/selectByName.sql" on line 4 at column 40.
The cause is as follows: [DOMA4067] The parameter that corresponds to the variable "name"
in the SQL statement at column 12 is not found in the method.
// SQLファイルを置き忘れた場合
[DOMA4019] The file "META-INF/exp/EmployeeDao/selectByName.sql" is not found in the classpath.
反対に、SELECT結果の列とエンティティのプロパティの対応はコンパイル時には分かりません。注釈処理はデータベースに接続しないためです。employee_name as emp_nmのように別名を付け間違えたSQLはコンパイルを通り、実行時にUnknownColumnException(DOMA2002)で落ちました。さらに、SELECTで列を書き漏らした場合は既定では例外にならず、該当プロパティがnullのまま返ります。これを実行時に検出したいなら、@Select(ensureResultMapping = true)を付けます。実測ではResultMappingException(DOMA2216)が対応の取れないプロパティ名を列挙して投げられました。
JPA・Hibernate・MyBatisとの役割分担
| フレームワーク | SQL | SQLの誤りの検出 | 関連の扱い |
|---|---|---|---|
| Doma | 開発者が記述 | コンパイル時 | 3.4.0以降は集約戦略 |
| JPA / Hibernate | JPQLから自動生成 | 実行時 | @OneToMany等 |
| MyBatis | 開発者が記述 | 実行時 | resultMapで定義 |
@OneToManyや@ManyToOneはJPAの注釈で、Domaには存在しません。Domaに宣言的な関連マッピングが入ったのは3.4.0(2025年2月8日)で、@AggregateStrategyを付けたインターフェースに@AssociationLinker付きの関数を並べ、任意のSELECT結果を集約に組み立てます。2系で関連を扱うなら、Criteria APIのassociateにラムダを渡して自分で詰め替えます。Java向けORM全体の比較はJava ORM(O/Rマッパー)の種類と選び方|Hibernate・MyBatis・jOOQを比較【2026年10月】にまとめています。
Doma 2とDoma 3の違いと移行判断
2.62.1で止まった2系と、3.14.1まで進む3系
GitHubのリリース履歴では、2系の最後は2.62.1(2024年8月29日)で、3日後の2024年9月1日に3.0.0が出ています。その後2系のリリースはありません。3系の最新は3.14.1(2026年10月4日)で、式言語のnull参照時の例外とカンマ直後の符号付き数値リテラルの字句解析を直したバグ修正版です。
| 版 | 公開日(JST) | 主な変化 |
|---|---|---|
| 2.62.1 | 2024-08-29 | 2系の最終版(Java 8以上) |
| 3.0.0 | 2024-09-01 | Java 17必須・旧API削除 |
| 3.1.0 | 2024-11-04 | QueryDsl(統合Criteria API) |
| 3.4.0 | 2025-02-08 | 集約戦略による関連マッピング |
| 3.14.1 | 2026-10-04 | 最新(バグ修正) |
2系の2.62.1はJava 8以上、3.0.0のリリースノートは「Require Java 17 as a mandatory prerequisite」と書いています。Java 17に上げられないなら2系に留まるしかありませんが、それは2年以上パッチの出ていないライブラリを使い続けるという判断です。逆向きの上限もあります。公式READMEの互換表でDoma 2が対応するのはJava 8から22までで、23以降は3系だけが対応しています。Java 25のLTSへ上げる計画があるなら、Spring Bootを使っていなくても3系への移行が前提になります。
公式スターターでのSpring Boot 4対応とDoma 3の必須要件
実務で移行の期限を決めるのは、DomaよりSpring Bootの側です。Spring Boot連携ライブラリdoma-spring-boot-starterの各版が依存するDomaとSpring Bootを、Maven Centralのpomで確認しました。
| starter | 公開日 | Doma | Spring Boot |
|---|---|---|---|
| 1.8.0 | 2024-04-19 | 2.58.0 | 2.7.16 |
| 2.0.0 | 2024-09-01 | 3系へ移行 | 3系 |
| 2.4.0 | 2025-04-27 | 3.6.0 | 3.3.10 |
| 3.0.0 | 2025-11-25 | 3.11.1 | 4.0.0 |
表のSpring Boot列はpomが参照するspring-boot-dependenciesの版です。doma-spring-bootのREADMEは3.0.0の要件を「Java 17+」「Doma 3.x+」「Spring Boot 4.x+」と明記しており、Doma 2をSpring Boot 4で動かす公式の組み合わせはありません。そしてSpring Boot 3.5のOSSサポートは2026年6月30日に終了しています。公式スターターを使っている構成では、サポート中の4.0系・4.1系へ上げる時点でDoma 3への移行も同時に必要になります。スターターを使わずConfigを自前で組む構成は、この要件の対象外です。Spring Bootの版ごとの期限はSpring Bootバージョン一覧とサポート期限(EOL)【2026年10月】最新版は4.1、4.0の無償サポートは12月末終了、4系の変更点はSpring Boot 4とは?最新バージョン4.1の変更点・新機能とSpring Boot 3との違いで確認できます。
Doma 3で削除されたAPIと、コンパイルで分かる移行箇所
公式のDoma 3.x Migration Guideが挙げる削除項目のうち、アプリケーションのコードに効くのは次の3つです。
@SingletonConfigの削除。シングルトンのConfigは自分で用意する@Daoのconfig要素の削除。Configは実装クラスのコンストラクタへ渡すNamingとNamingTypeのLENIENT_SNAKE_LOWER_CASE・LENIENT_SNAKE_UPPER_CASEの削除。SNAKE_LOWER_CASE・SNAKE_UPPER_CASEへ置き換える
3つとも、2系の書き方のままDoma 3.14.1でコンパイルすると「シンボルを見つけられません」で止まりました。この3つについては、ビルドを通す過程で置き換え箇所が見つかります。残りの削除項目はEntityType#getTableNameやStandardDialectのメソッド名の綴り修正など、フレームワークを拡張していなければ触らない範囲です。ただし移行ガイドは3.3への更新でもStandardJdbcMappingVisitor#visitObjectWrapperの変更を挙げており、これを上書きした独自拡張は変更しなくてもコンパイルが通ったまま新機能だけが効かなくなります。Dialectなどを拡張している場合は、ガイドの各項目を個別に確認してください。SQLテンプレートの書式は変わっていません。
導入手順:依存関係と注釈処理の設定
使う成果物はgroupId org.seasar.domaの2つです。doma-coreは実行時とコンパイル時の両方、doma-processorはコンパイル時だけ必要です。Gradleでは公式がorg.domaframework.doma.compileプラグインを推奨しています。公式ドキュメントのGroovy DSLの例は3.0.1のままですが、ドキュメントの設定値(doma_compile_version)は4.0.3です。4.0.0以降はDoma 3.8.0以上が対象なので、新しく組むなら4.0.3を使います。
plugins {
id 'java'
id 'org.domaframework.doma.compile' version '4.0.3'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.seasar.doma:doma-core:3.14.1'
annotationProcessor 'org.seasar.doma:doma-processor:3.14.1'
}
Mavenではmaven-compiler-pluginのannotationProcessorPathsにdoma-processorを置きます。今回の検証ではこの設定でJDK 25からビルドしました。-Adoma.resources.dirはEclipseでMavenプロジェクトを扱うときにSQLファイルを見つけさせるための引数です。
<properties>
<maven.compiler.release>17</maven.compiler.release>
<doma.version>3.14.1</doma.version>
</properties>
<dependencies>
<dependency>
<groupId>org.seasar.doma</groupId>
<artifactId>doma-core</artifactId>
<version>${doma.version}</version>
</dependency>
</dependencies>
<build><plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.seasar.doma</groupId>
<artifactId>doma-processor</artifactId>
<version>${doma.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Adoma.resources.dir=${project.basedir}/src/main/resources</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins></build>
データベーススキーマからエンティティ・DAO・SQLファイルを生成したい場合は、GradleのDoma CodeGen Plugin(v3.2.2)を使います。Ant版のDoma-Genは公式ドキュメントが「no longer maintained」と明記しているので、新規には使いません。
エンティティクラスとDAOインターフェースの定義
エンティティのマッピング規則と採番の落とし穴
エンティティは@Entityを付けたクラスです。テーブル名・カラム名の変換規則はnaming要素で指定し、個別に変えたいときだけ@Tableと@Columnで上書きします。NamingTypeはorg.seasar.doma.jdbc.entityパッケージにあるため、org.seasar.doma.*だけをimportしてもコンパイルは通りません。
import org.seasar.doma.*;
import org.seasar.doma.jdbc.entity.NamingType;
@Entity(naming = NamingType.SNAKE_LOWER_CASE, metamodel = @Metamodel)
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer id;
public String employeeName;
public Integer departmentId;
@Version
public Integer version;
}
@GeneratedValueの戦略はIDENTITY・SEQUENCE・TABLEの3つです。公式ドキュメントのエンティティの章は、採番した値を代入するのは「フィールドがnullか0未満のときだけ」と定めています。実際にint id(プリミティブ型)で宣言して初期値0のまま登録すると、データベース側では行が採番されてもidは0のままでした。-1で初期化すると採番値が入ります。IDをIntegerで宣言しておけば、この罠は踏みません。@Versionを付けたプロパティは、値を設定せずに登録すると1が入り(実測でも1)、更新のたびに増えて楽観的排他制御に使われます。排他制御の考え方は楽観ロックと悲観ロックとは?違い・使い分け・実装をDB別に解説【2026年版】を参照してください。
DAOインターフェースと生成される実装クラス
DAOはインターフェースとして定義し、@Daoを付けます。実装クラスはコンパイル時に「インターフェース名+Impl」で生成されます。Doma 3ではConfigを実装クラスのコンストラクタで渡します。
@Dao
public interface EmployeeDao {
@Select
List<Employee> selectByName(String employeeName, SelectOptions options);
@Sql("select /*%expand*/* from employee where department_id = /* departmentId */1 order by id")
@Select
List<Employee> selectByDept(Integer departmentId, SelectOptions options);
@Sql("select /*%expand*/* from employee where id = /* id */1")
@Select
Employee selectById(Integer id);
@Insert
int insert(Employee employee);
@Update
int update(Employee employee);
}
// 呼び出し側
EmployeeDao dao = new EmployeeDaoImpl(config);
2つ目のメソッドのように、@Sqlを使えばSQLファイルを置かずに注釈の中へテンプレートを書けます。DAOとエンティティを1対1にする必要はなく、1つのDAOが複数のエンティティを扱っても構いません。insert・updateのように@SqlもSQLファイルも持たない更新系メソッドは、エンティティの定義からSQLが自動生成されます。エンティティという設計要素の位置づけはエンティティクラスとは|JPAとEF Coreの書き方・DTOとの違いを実例で解説、DAOを呼ぶ層の切り方はサービスクラスとは?Java・Springでの役割と書き方、コントローラーとの違いで扱っています。
SQLテンプレート(2WaySQL)の配置規約とディレクティブ
SQLファイルのパス規約とRDBMS別の切り替え
公式ドキュメントのSQLテンプレートの章が定めるパスはMETA-INF/(DAOの完全修飾名をスラッシュ区切りにしたパス)/(メソッド名).sqlです。DAOがaaa.bbb.EmployeeDao、メソッドがselectByIdならMETA-INF/aaa/bbb/EmployeeDao/selectById.sqlになります。「META-INF/sqlフォルダにまとめる」という説明は誤りで、DAOのパッケージを移したらSQLファイルも一緒に移します。文字コードはUTF-8です。
データベースごとにSQLを書き分けるときは、拡張子の前にハイフンとRDBMS名を挟みます。selectById-postgres.sqlがあれば、PostgreSQLではそちらが優先されます。使える名前はdb2・h2・hsqldb・mssql・mysql・oracle・postgres・sqliteです。
条件分岐・繰り返し・列展開のディレクティブ
2WaySQLは、テンプレートをそのままSQLツールで実行できる形を保つ書き方です。バインド変数は/* id */0のようにコメントとテストデータの組で書き、実行時にはまとめて?へ置き換わります。
select /*%expand*/* from employee
where
/*%if employeeName != null */
employee_name like /* @prefix(employeeName) */'A%' escape '$'
/*%end*/
order by id
前方一致の@prefix関数は、値の中の%と_を既定のエスケープ文字$で退避します。そのためLIKE句にはescape '$'が必要です。上のSQLで「Alice」「AB%C」「ABXC」「Bob」「Anna」の5行を検索すると、引数"A"では4行、"AB%"では「AB%C」の1行だけが返りました。%がワイルドカードとして扱われていれば「ABXC」も一致するので、エスケープが効いていることが分かります。employeeNameがnullのときはwhereだけが残りますが、Domaは条件が空になったwhereを出力時に取り除きます。
公式が挙げるディレクティブは8種類です。バインド変数/* */、リテラル変数/*^ */、埋め込み変数/*# */、条件分岐/*%if*/(/*%elseif*/・/*%else*/・/*%end*/を伴う)、繰り返し/*%for 変数 : コレクション*/、SELECT句の列展開/*%expand*/、UPDATEのSET句の展開/*%populate*/、解析時に取り除かれるコメント/*%! */です。埋め込み変数はORDER BYの列名のようにバインドできない箇所へ値を直接埋め込むもので、Domaはシングルクォートやセミコロンを含む文字列を拒否しますが、列名のホワイトリスト検証はアプリケーション側の責任です。他のSQLファイルを取り込む#includeのようなディレクティブは存在しません。動的SQLの設計の違いはMyBatis Dynamic SQLの使い方|if・where・foreachと2.0.0の型安全DSLを実行結果で解説と比べると見えてきます。
ページングと件数取得:SelectOptions
ページングはSQLにlimitを書かず、DAOメソッドの引数にSelectOptionsを足して指定します。count()を付けると、同じ条件の総件数も1回の呼び出しで取れます。
SelectOptions options = SelectOptions.get().offset(1).limit(2).count();
List<Employee> page = dao.selectByName(null, options);
long total = options.getCount();
// 実測:page = [AB%C, ABXC]、total = 5
offsetとlimitの書き換えは方言クラスが担います。公式ドキュメントの検索の章によれば、SQL Server(MssqlDialect)でoffsetを使うにはORDER BY句が必須で、Db2ではORDER BYの列をすべてSELECT句に含める必要があります。悲観的排他制御が要る場面では、同じSelectOptionsのforUpdate()を使います。
Query DSLによる動的SQLの組み立て
SQLファイルを持たずにJavaコードでクエリを組む経路もあります。Doma 3のQueryDslは、2系のEntityqlとNativeSqlを1つに統合したAPIで、3.1.0で入りました。エンティティに@Entity(metamodel = @Metamodel)を付けると、注釈処理がメタモデルクラスEmployee_を生成します。
var e = new Employee_();
List<Employee> list = new QueryDsl(config)
.from(e)
.where(c -> c.eq(e.departmentId, 10))
.orderBy(c -> c.asc(e.id))
.fetch();
カラム名を文字列で書かないので、プロパティ名を変えればコンパイラが呼び出し箇所を追えます。条件の組み合わせが画面ごとに変わる検索はQueryDsl、結合と集計が中心のSQLはテンプレート、と分けると読みやすさを保てます。名前の似たQueryDSLとは?Spring Bootでの導入手順と5.1.0・OpenFeign版7.5の選び方のQuerydslは、Domaとは別のライブラリです。
大量データの一括登録:@BatchInsertと@MultiInsertの実測
@BatchInsertの既定batchSizeとexecuteBatchの実行単位
Domaで複数行を登録する注釈は2つあります。@BatchInsertはJDBCのバッチ(addBatch/executeBatch)で同じINSERT文を繰り返す方式、@MultiInsertはvalues (...), (...)の1文にまとめる方式です。ここで問題になるのが@BatchInsertの既定値です。Config#getBatchSize()の既定は0で、BatchModifyCommandは0以下を1として扱います。つまりbatchSizeを指定しない@BatchInsertは、1行ごとにexecuteBatch()を呼びます。この実装は2.54.1・2.62.1・3.14.1で同じです。Spring Boot連携のdoma.batch-sizeプロパティも既定は0なので、スターター経由でも状況は変わりません。
@BatchInsert // Configの既定のまま=1行ずつ送信
int[] batchInsert(List<Employee> employees);
@BatchInsert(batchSize = 1000) // 1,000行ごとにexecuteBatch()
int[] batchInsert1000(List<Employee> employees);
@MultiInsert // values (...), (...) の1文
int multiInsert(List<Employee> employees);
なお、SQLログはどちらの@BatchInsertでも1行ごとに1件出力されます。ログの件数を見て「バッチになっていない」と判断することはできないので、送信回数はデータベース側で数えます。
MySQLで1万件を登録した実測値
MySQL 8.4.8・Connector/J 26.7.0・Doma 3.14.1・JDK 25を同じMacBook Pro(Intel Core i9-9880H・メモリ16GB)上で動かし、1万件を1トランザクションで登録しました。テーブルはid integer auto_increment primary key, employee_name varchar(100) not null unique, department_id integer, version integerで、毎回作り直しています。計時はトランザクション開始からコミットまでで、DomaのSQLログ出力は止めています。各方式を4回ずつ実行し、ウォームアップとして初回を除いた3回の中央値を載せました。INSERT回数は、他の接続がない状態でサーバーのCom_insert(GLOBAL)の前後差を取った値です。
| 方式 | INSERT回数 | 所要時間 |
|---|---|---|
| @Insertをforで1万回 | 10,000 | 約4.1秒 |
| @BatchInsert(既定) | 10,000 | 約6.1秒 |
| @BatchInsert(既定)+rewrite | 10,000 | 約5.6秒 |
| @BatchInsert(batchSize=1000) | 10,000 | 約3.4秒 |
| batchSize=1000+rewrite | 10 | 約0.7秒 |
| @MultiInsert | 1 | 約0.7秒 |
「rewrite」はJDBC URLにrewriteBatchedStatements=trueを付けた条件です。MySQLのConnector/Jはこのオプションが既定でfalseで、falseのままではバッチでもINSERT文を1つずつ送ります。batchSizeだけを設定すると所要時間は約6.1秒から約3.4秒に縮みましたが、INSERTは1万回のままでした。rewriteだけでは約5.6秒と、ほとんど変わりません。両方そろえて初めて1,000行が1文に書き換えられ、INSERTは10回、所要時間は約0.7秒になりました。@MultiInsertは設定なしで1回です。どちらの方式でも、IDENTITYで採番されたIDは1万件目まで各エンティティに戻っていました。
既定の@BatchInsertがforループより遅かったのは、全行分のSQLを先に組み立ててから送る実装のためと考えられます。公式ドキュメントも、数十万行規模ではbatchSizeを設定していてもこの事前組み立てでヒープを使い切ることがあるとして、ChunkedAutoBatchInsertQueryへの差し替えを案内しています。
@BatchInsertと@MultiInsertの選び方
MySQL・PostgreSQL・H2で大量登録をするなら、まず@MultiInsertを検討します。設定なしで1文にまとまり、duplicateKeyTypeで重複キー時の更新や無視(いわゆるupsert)も書けます。ただし公式ドキュメントによれば、対応はH2・MySQL・PostgreSQL・SQL Server・Oracleで、SQL ServerとOracleでは自動採番の主キーを持つテーブルに使えません。また1文の大きさはMySQLのmax_allowed_packet(8.4の既定は64MB)に縛られるので、数十万件ならリストを分割して呼びます。
@BatchInsertを使い続ける場合は、batchSize(注釈かConfigのgetBatchSize()、Spring Bootならdoma.batch-size)を必ず設定し、MySQLならJDBC URLにrewriteBatchedStatements=trueを足します。batchSizeだけでも時間は縮みますが、送信するINSERT文の数が減るのは両方を設定したときだけです。
トランザクション制御とSpring Boot連携
素のDoma:TransactionManagerのラムダ
Doma単体では、トランザクション境界を注釈ではなくAPIで作ります。ConfigのgetDataSource()がLocalTransactionDataSourceを返し、そこから作ったLocalTransactionManagerで制御します。この記事の検証コードもこの構成です。
public class DbConfig implements Config {
private final Dialect dialect = new MysqlDialect();
private final LocalTransactionDataSource dataSource =
new LocalTransactionDataSource("jdbc:mysql://localhost:3306/app", "user", "password");
private final TransactionManager tm =
new LocalTransactionManager(dataSource.getLocalTransaction(getJdbcLogger()));
public Dialect getDialect() { return dialect; }
public DataSource getDataSource() { return dataSource; }
public TransactionManager getTransactionManager() { return tm; }
}
// 呼び出し側
config.getTransactionManager().required(() -> {
Employee employee = dao.selectById(1);
employee.employeeName = "hoge";
dao.update(employee);
});
開始方法はrequired(既存があれば参加、なければ新規)、requiresNew(常に新規)、notSupported(トランザクション外で実行)の3つです。ラムダが正常に終わればコミット、例外が出ればロールバックします。Domaのorg.seasar.domaパッケージに@Transactionalや@Cacheableという注釈はありません。これらをDomaの機能として説明している資料は、Springの注釈と取り違えています。トランザクションの基礎はトランザクションとは?分離レベルの違い・ACID・commit/rollbackを実装目線で解説【2026年版】にまとめています。
Spring Boot連携:@Transactional・DataSourceのラップ・例外変換
doma-spring-boot-starterを入れると、DomaのConfigが自動構成され、Springのデータソースとトランザクション管理につながります。この構成では境界をSpringの@Transactionalで宣言し、DomaのLocalTransactionManagerは使いません。
つながる理由はデータソースのラップにあります。スターターのDomaConfigBuilderは、渡されたDataSourceをTransactionAwareDataSourceProxyで包みます。これでDAOが取得する接続が、Springのトランザクションが握っている接続と同じになります。このため、Configを自分でBean定義すると自動構成が働かず、getDataSource()で同じラップをしない限り@Transactionalの外で接続が取られます。複数データソースのためにConfigを自作するときに起きやすい事故です。
doma.dialect=mysql
doma.naming=snake_lower_case
doma.batch-size=1000
doma.exception-translation-enabled=true
設定はapplication.propertiesのdoma.で始まるプロパティで行います。doma.exception-translation-enabledは既定でtrueで、Domaの例外をSpringのDataAccessException系へ変換します。たとえば一意制約違反は、Doma単体ではUniqueConstraintException(DOMA2004、バッチならBatchUniqueConstraintException)として投げられ、Spring連携ではDuplicateKeyExceptionに、楽観的排他の失敗はOptimisticLockingFailureExceptionに変わります。catchする側はSpringの例外クラスで書きます。META-INFにdoma.propertiesを置いて接続情報を書く、という設定方式はDomaにありません。
Domaには遅延ロードがなく、SQLはDAOメソッドかQueryDslのfetch()などを呼んだときにだけ発行されます。JPAのように関連を辿っただけで追加のSQLが飛ぶことはないので、Open Session in Viewの議論は起きません。ただし、取得した一覧をループしながら1件ずつDAOで検索すれば、DomaでもN+1問題はそのまま起きます。JPA側で何が起きるかはN+1問題とは?発行クエリ数の実測と検出方法・ORM別の対策とspring.jpa.open-in-viewとは?OSIVの既定値trueと起動時警告の消し方・falseの判断基準で扱っています。
Domaを採用すべきでない条件
最も重いのは、Java 17へ上げられないまま長期運用する新規プロジェクトです。使えるのは2系だけで、2系は2024年8月29日以降リリースがなく、公式READMEの区分は「limited-support」(3系は「stable」)です。脆弱性の修正が出るかどうかを当てにできない前提で、自前で修正する体制がないなら、Java自体の更新を先に計画に載せるべきです。
次に、1つのコードベースで多数のRDBMSを本番稼働させ、しかもDB固有の構文を多用する製品です。共通SQLはそのまま使い回せますし、QueryDslなら方言クラスがSQLを組み立てますが、DB固有の構文に頼るSQLは-mysql.sql・-postgres.sqlのファイル分岐で書き分けることになり、対応DBが増えるほど保守するSQLが増えます。SQLの書き分け自体を減らしたいなら、JPAと比べてから決めます。
単純なCRUDが大半で、立ち上げの速さを優先する小規模開発にも向きません。注釈処理の設定とSQLファイルのパス規約という初期コストが、Spring Data JPAのメソッド名からのクエリ生成に見合わないからです。逆に、SQLのチューニングが性能要件に直結する業務システムや、複雑な結合・集計を書き続ける領域ではDomaの強みが最も出ます。既存システムのデータアクセス層の移行やDB設計の見直しまで含めて検討する場合は、データベース設計・移行支援でも相談を受けています。
よくある質問
DomaとDoma2は別のものですか?
同じ製品です。「Doma2」は2系(2.x)を指す通称で、公式ドキュメントも2系のトップを「Doma 2へようこそ」としています。単に「Doma」と書かれた最新の資料は3系を指します。
Doma3とDoma2の違いは何ですか?
Doma 3はJava 17以上が必須で、@SingletonConfig・@Daoのconfig要素・LENIENT_SNAKE_*が削除されています。QueryDsl(3.1.0)や集約戦略による関連マッピング(3.4.0)は3系にしかありません。2系は2.62.1で更新が止まっています。
Spring BootでDoma2を使い続けられますか?
Doma 2向けのスターターは1.8.0が最後で、Spring Boot 4に対応するスターター3.0.0はDoma 3.x以上が要件です。Spring Boot 3.5のOSSサポートは2026年6月30日に終了しているため、公式スターターのままSpring Bootを上げるならDoma 3へ移行することになります。
@BatchInsertが速くならないのはなぜですか?
batchSizeの既定が0で、1行ずつexecuteBatch()されるからです。batchSizeを設定し、MySQLではJDBC URLにrewriteBatchedStatements=trueを足します。設定なしで1文にまとめたいなら@MultiInsertを使います。
Doma 3に上げるとSQLファイルは書き直しになりますか?
なりません。SQLテンプレートの書式は2系と同じです。アプリケーション側の主な作業はJava 17への引き上げと、削除された3つのAPIの置き換えで、この3つはコンパイルエラーとして見つかります。Dialectなどフレームワークを拡張している場合は、移行ガイドの残りの項目も確認します。